3.6 编译函数
从Sizzle 1.8.0开始,Sizzle开始引入编译函数机制,主要作用是分词的筛选,提高逐个匹配的效率。
3.6.1 元匹配器
通过tokenize处理器分类的group都有对应的type,每种type都会有对应的处理方法,源代码如下:
可以把“元”理解为“原子”,也就是最小的那个匹配器。在CSS选择器中最小单元可以划分为ATTR、CHILD、CLASS、ID、PSEUDO、TAG。在Sizzle中有一些工厂方法用来生成对应的这些元匹配器,如Expr.filter。
下面可以看看属性选择器的处理代码:
实际上,Sizzle通过对selector做“分词”,打散之后再分别从Expr.filter里面去找对应的方法来执行具体的查询或者过滤的操作。
3.6.2 编译器
为了提高效率,Sizzle引入了编译函数的概念。通过Sizzle.compile方法内部的matcherFromTokens和 matcherFromGroupMatchers把分析关系表生成用于匹配单个选择器群组的函数。
matcherFromTokens充当了selector的“分词”,与Expr中定义的匹配方法的纽带,可以说选择符的各种排列组合都是能适应的。Sizzle的巧妙之处在于没有直接将拿到的“分词”结果与Expr中的方法逐个匹配并执行,而是先根据规则组合出一个大的匹配方法,最后一步执行。编译器的源代码如下:
3.6.3 过滤函数
matcherFromTokens通过解析selector获得对应的过滤函数,源代码如下:
在上面代码中,重点是:
cached = matcherFromTokens(group[i]);
cached的结果就是matcherFromTokens返回的matchers编译函数。matcherFromTokens的分解是有规律的:语义节点+关系选择器的组合。
div > p+div.sub input[type="checkbox"]
Expr.relative匹配关系选择器类型,当遇到关系选择器时,elementMatcher函数将matchers数组中的函数生成一个函数。
再递归分解tokens中的词法元素,提取第一个typ匹配到对应的处理方法:
matcher = Expr.filter[tokens[i].type].apply(null, tokens[i].matches);
例如,下面是TAG类型源代码。
matcher最终返回的结果就是布尔值,但是这里返回的只是一个闭包函数,不会马上执行,这个过程就是编译成一个匿名函数。
如果遇到关系选择符就会合并分组了。
matchers = [addCombinator(elementMatcher(matchers), matcher)];
通过elementMatcher生成一个终极匹配器。
上面代码将分解这个子匹配器,返回一个curry函数,传递给addCombinator()函数。addCombinator()函数的源代码如下:
matcher为当前词素前的“终极匹配器”,combinator为位置词素。根据关系选择器检查,如果是没有位置词素的选择器,如#id.sub[name="checkbox"],则从右到左依次查看当前节点elem是否匹配规则即可。
由于有了位置词素,那么判断的时候就不能简单判断当前节点,可能需要判断elem的兄弟节点或者父亲节点是否依次符合规则。
这是一个递归深度搜索的过程,所以matchers又经过一层包装,然后使用相同的方式递归,直到tokens分解完毕。
返回的结果是一个根据关系选择器分组后再组合的嵌套很深的闭包函数。