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返回的是集合。