天天看点

dojo/query源码解析

dojo/query模块是dojo为开发者提供的dom查询接口。该模块的输出对象是一个使用css选择符来查询dom元素并返回nodelist对象的函数。同时,dojo/query模块也是一个插件,开发者可以使用自定义的查询引擎,query模块会负责将引擎的查询结果包装成dojo自己的nodelist对象。

  要理解这个模块就要搞清楚两个问题:

如何查询,查询的原理?

查询结果是什么,如何处理查询结果?

  这两个问题涉及到本文的两个主题:选择器引擎和nodelist。

选择器引擎

  前端的工作必然涉及到与dom节点打交道,我们经常需要对一个dom节点进行一系列的操作。但我们如何找到这个dom节点呢,为此我们需要一种语言来告诉浏览器我们想要就是这个语言描述的dom节点,这种语言就是css选择器。比如我们想浏览器描述一个dom节点:div > p + .bodhi input[type="checkbox"],它的意思是在div元素下的直接子元素p的下一个class特性中含有bodhi的兄弟节点下的type属性是checkbox的input元素。

  选择符种类

元素选择符:通配符*、类型选择符e、类选择符e.class、id选择符e#id

关系选择符:包含(e f)、子选择符(e>f)、相邻选择符(e+f)、兄弟选择符(e~f)

属性选择符: e[att]、e[att="val"]、e[att~="val"]、e[att^="val"]、e[att$="val"]、e[att*="val"]

伪类选择符

伪对象选择符:e:first-letter、e:first-line、e:before、e:after、e::placehoser、e::selection

  通过选择器来查询dom节点,最简单的方式是依靠浏览器提供的几个原生接口:getelementbyid、getelementsbytagname、getelementsbyname、getelementsbyclassname、queryselector、queryselectorall。但因为低版本浏览器不完全支持这些接口,而我们实际工作中有需要这些某些高级接口,所以才会有各种各样的选择器引擎。所以选择器引擎就是帮我们查询dom节点的代码类库。

  选择器引擎很简单,但是一个高校的选择器引擎会涉及到词法分析和预编译。不懂编译原理的我表示心有余而力不足。

  但需要知道的一点是:解析css选择器的时候,都是按照从右到左的顺序来的,目的就是为了提高效率。比如“div p span.bodhi”;如果按照正向查询,我们首先要找到div元素集合,从集合中拿出一个元素,再找其中的p集合,p集合中拿出一个元素找class属性是bodhi的span元素,如果没找到重新回到开头的div元素,继续查找。这样的效率是极低的。相反,如果按照逆向查询,我们首先找出class为bodhi的span元素集合,在一直向上回溯看看祖先元素中有没有选择符内的元素即可,孩子找父亲很容易,但父亲找孩子是困难的。

选择器引擎为了优化效率,每一个选择器都可以被分割为好多部分,每一部分都会涉及到标签名(tag)、特性(attr)、css类(class)、伪节点(persudo)等,分割的方法与选择器引擎有关。比如选择器 div > p + .bodhi input[type="checkbox"]如果按照空格来分割,那它会被分割成以下几部分:

div

>

p

+

.bodhi

input[type="checkbox"]

 对于每一部分选择器引擎都会使用一种数据结构来表达这些选择符,如dojo中acme使用的结构:

 从这里可以看到有专门的结构来管理不同的类型的选择符。分割出来的每一部分在acme中都会生成一个part,part中有tag、伪元素、属性、元素关系等。。;所有的part都被放到queryparts数组中。然后从右到左每次便利一个part,低版本浏览器虽然不支持高级接口,但是一些低级接口还是支持的,比如:getelementsby*;对于一个part,先匹配tag,然后判断class、attr、id等。这是一种解决方案,但这种方案有很严重的效率问题。(后面这句是猜想)试想一下:我们可不可以把一个part中有效的几项的判断函数来组装成一个函数,对于一个part只执行一次即可。没错,acme就是这样来处理的(这里涉及到预编译问题,看不明白的自动忽略即可。。。)

  

  dojo/query模块的选择器引擎通过dojo/selector/loader来加载。如果没有在dojoconfig中配置selectorengine属性,那么loader模块会自己判断使用acme和是lite引擎,原则是高版本浏览器尽量使用lite,而低版本尽量使用acme。

  选择器引擎的代码晦涩难懂,我们只需要关心最终暴露出来的接口的用法即可。

  acme:

  lite:

nodelist

  nodelist来自于原生dom,是一系列dom节点的集合,一个类素组对象,mdn中的解释:

  我们看到document.queryselectorall方法返回一个nodelist对象,而且这个方法返回的nodelist对象是一个静态的集合。

  所以大多数的前端类库都参照原生设计,查询接口返回的都是一个静态集合。只是有的明确点明如dojo,有的含蓄的实现如jquery。

  要了解dojo中的nodelist需要把握以下规则:

dojo中的nodelist就是扩展了能力的array实例。所以需要一个函数将原生array包装起来

nodelist的任何方法返回的还是nodelist实例。就像array的slice、splice还是返回一个array一样

  has("array-extensible")的作用是判断数组是否可以被继承,如果原生的数组是可被继承的,那就将nodelist的原型指向一个数组实例,否则指向普通对象。

  下面这句话需要扎实的基本功,如果理解这句话,整个脉络就会变得清晰起来。

  new的作用等于如下函数:

  放到nodelist身上是这样的:

  isnew为true,保证了nodelist实例是一个经过扩展的array对象。

  nodelist函数的源码:

  可以看到如果isnew为false,那就对一个新的array对象进行扩展。

  扩展的能力,便是直接在nodelist.prototype上增加的方法。大家直接看源码和我的注释即可。

  nodelist提供的很多操作,如:map、filter、concat等,都是借助原生的array提供的相应方法,这些方法返回都是原生的array对象,所以需要对返回的array对象进行包装。有趣的是nodelist提供end()可以回到原始的nodelist中。整个结构如下:

dojo/query源码解析

  我们来看一下包装函数:

  这就是dojo中nodelist的设计!

  query模块暴露的方法无非就是对选择器引擎的调用,下面就比较简单了。

如果您觉得这篇文章对您有帮助,请不吝点击推荐,您的鼓励是我分享的动力!!!

继续阅读