jQuery从入门到精通(微课精编版)
上QQ阅读APP看书,第一时间看更新

3.7 超级匹配

通过Expr.find[ type ]找出选择器最右边的最终seed种子合集;通过Sizzle.compile函数编译器,把tokenize词法元素编译成闭包函数;使用superMatcher超级匹配,以最佳的方式从seed种子集合筛选出需要的数据,也就是通过seed与compile的匹配,得出最终的结果。

3.7.1 superMatcher

superMatcher并不是一个直接定义的方法,它通过matcherFromGroupMatchers方法返回的一个Curry化的函数,但是最后执行起重要作用的是它。

     compile( selector, match )(
         seed,
         context,
         !documentIsHTML,
         results,
         rsibling.test( selector ) && testContext( context.parentNode ) || context
     );

superMatcher方法会根据参数seed、expandContext和context确定一个起始的查询范围。

     elems = seed || byElement && Expr.find["TAG"]( "*", outermost ),

有可能直接从seed中查询过滤,也有可能在context或者context的父节点范围内。如果不是从seed开始,那只能把整个DOM树节点取出来过滤了,把整个DOM树节点取出来过滤后,它会先执行Expr.find["TAG"]( "*", outermost )这句代码等到一个elems集合(数组合集)。

     context.getElementsByTagName( tag );

可以看出对于优化选择器,最右边应该写一个作用域的搜索范围context比较好。

开始遍历这个seed种子合集:

elementMatchers就是通过分解词法器生成的闭包函数,也就是“终极匹配器”。

tokenize选择器可以用“,”分组group,所以就有个合集的概念。matcher就得到每个终极匹配器。通过代码能看出matcher方法运行的结果都是布尔值。

对里面的元素逐个使用预先生成的matcher方法做匹配,如果结果为true,则直接将元素推入返回结果集里。

3.7.2 matcher

matcher是elementMatcher函数的包装,整个匹配的核心代码如下:

Sizzle引擎的解析过程如图3.6所示。

图3.6 Sizzle引擎的解析过程

解析过程:

第1步,设计CSS选择器。

     div > p+div.sub input[type="checkbox"]

从右边剥离出原生API能使用的接口属性:

     context.getElementsByTagName("input" )

找到input,因为只可以使用TAG查询,但是此时结果是个合集,故引入seed的概念,称为种子合集。

第2步,重组选择器,去掉input,得到新的tokens词法元素哈希表。

     div > p+div.sub [type="checkbox"]'

第3步,调用matcherFromTokens函数,根据关系选择器(">","空","~","+")分组,因为DOM节点都是存在关系的,所以引入Expr.relative,通过first:true得到两个关系的“紧密”程度,用于组合最佳的筛选。

按照如下顺序解析,且编译闭包函数。

编译规则:div > p+div.sub [type="checkbox"]

编译成4组闭包函数,然后前后再合并组合成一组。

div >

p+

div.sub

input[type="checkbox"]

先构造一组编译函数:

A:抽出div元素,对应的是TAG类型。

B:通过Expr.filter找到对应匹配的处理器,返回一个闭包处理器,如TAG方法。

C:将返回的Curry方法放入matchers匹配器组中,继续分解。

D:抽出子元素选择器'>',对应的类型为type: ">"。

E:通过Expr.relative找到elementMatcher方法,分组合并多个词素的编译函数。

执行各自Expr.filter匹配中的判断方法,其中matcher方法运行的结果都是布尔值,所以这里只返回了一个组合闭包,通过这个筛选闭包,各自处理自己内部的元素。

F:返回匹配器还是不够,因为没有规范搜索范围的优先级,所以这时还要引入addCombinator方法。

G:如果Expr.relative和first:true两个关系的“紧密”程度高,则返回addCombinator。

如果是紧密关系的位置词素,找到第一个亲密的节点,用终极匹配器判断这个节点是否符合前面的规则。

上面是第一组终极匹配器的生成流程,可见过程极其复杂,被包装了三层,依次是addCombinator、elementMatcher和Expr.relative。

三个方法嵌套处理。然后继续分解下一组,遇到关系选择器又继续依照以上的过程分解。但是有一个不同的地方,下一个分组会把上一个分组一并合并,所以整个关系就是一个依赖嵌套很深的结构。

可以看到,终极匹配器其实只有一个闭包,但是有内嵌很深的分组闭包,依照从左往右依次生成闭包,然后把上一组闭包添加到下一组闭包,就跟栈是一种后进先出的数据结构一样处理,所以最外层是type=["checkbox"]。

再返回superMatcher方法的处理。遍历seed种子合集,依次匹配matchers闭包函数,传入每个seed的元素与之匹配(这里是input),在对应的编译处理器中通过对input的处理,找到最优匹配结果。

注意:i--表示从后往前查找,所以第一次开始匹配的是:check: "checkbox"、name: "type"、operator: "="。

找到对应的Attr处理方法,源代码如下:

例如:

     Sizzle.attr( elem, name )

传入elem元素就是seed中的input元素,找到是否有'type'类型的属性,如<input type="text">,所以第一次匹配input就出错了,返回的type是text,而不是需要的'checkbox',这里返回的结果就是false,所以整个处理就直接返回。

再传入第二个input,继续上一个流程,这时发现检测到的属性:

     var result = Sizzle.attr( elem, name );
     result: "checkbox"

此时满足第一条匹配,然后继续i = 0。

     !matchers[i]( elem, context, xml )

找到第0个编译函数—addCombinator(),源代码如下:

如果是不紧密的位置关系,那么一直匹配到true为止。例如祖宗关系,查找父亲节点直到有一个祖先节点符合规则为止。

直接递归调用代码如下:

     matcher( elem, context, xml )

就是下一组闭包队列,传入的上下文是div.sub,也就是<input type="checkbox">的父节点。

这样递归下去,一层一层地匹配,可见不是一层一层往下查,而是一层一层向上做匹配、过滤。Expr里面只有find和preFilter返回的是集合。