【转载】 jQuery源码分析-04 选择器-Sizzle-从左向右的余热
在Sizzle的设计思路中,将CSS选择器引擎的通用设计思路进行了分解和推理;在Sizzle的工作原理中,对Sizzle创造性的从右向左算法,进行了详细的分析。
但从右向左并不适用所有的选择器表达式,本文将分析从左向右的必要性和应用场景,以及为什么从右向左效率更高的原因。
先看下边这个经典的例子:
$(‘div button:first’) | :first是位置过滤器,div下所有button中的第一个,只匹配一个元素 |
$(‘div button:first-child’) | :first-child是子元素过滤器,每个div下button中的第一个,为每个父元素div匹配一个子元素button(准确的说,button必须是div的第一个子元素才会匹配) |
我们对含有位置过滤器的$(‘div button:first’)试试从右向左会发生什么:
1. 选择查找button:first,先根据tag找出所有button,然后用集合过滤器Expr.setFilters.first找到第一个button
2. 第2步?没有第2步!第1步已经错误,第一步找的是所有button的第一个,而我们要找的是div下的button中的第一个
我们再分析一下:first和:first-child的过滤对象有什么区别:
:first的过滤对象是’div button’的查找/过滤结果,依赖于前一个块表达式
:first-child的过滤对象是’button’自身,如何理解呢?要检查’button’是否其父元素下的第一个元素,通过’button’本身就可以执行这个过滤,因为通过’button’就可以定位到父元素,然后再检查过滤到的’button’的祖先是否div
这里所说的“依赖”的概念有些模糊,因为只要有多个块表达式,后边的必然总是依赖于前边的,天生如此,后边的是前边的子元素、子孙元素、兄弟元素。
这里所说的“依赖”指的不是块表达式之间这种天生的关系,而是单个块表达式的过滤无法独立进行,必须在前一个块表达式的查找/过滤结果确定之后,才能进行当前块表达式的过滤的情况
$(‘div button:first’)的工作流程应该是从左向右:
1. 找到div
2. 找到所有div下的所有button
3. 找到所有button中的第一个
可见从右向左不是万能的,必要时仍然需要传统的从左向右,什么时候需要从左向右呢?
答案是有位置过滤器的时候。
因为位置过滤器所在的块表达式的过滤结果,依赖于它前一个的块表达式查找/过滤的结果,所有要先查找/过滤前一个,即从左向右;而其他过滤器仅依赖于当前元素本身。看看Sizzle源码是如何判断从左向右还是从右向左的:
// 匹配到多个块选择器,并且是位置过滤器(伪类的一种) /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/ if ( parts.length > 1 && origPOS.exec( selector ) ) { // 从左向右 // some code … } else { // 从右向左 // some code … } |
将从左向右的工作流程整理如下:
1. 使用块分割器对选择器表达式进行分割,从左向右
—– 以下是从左向右 —–
2. 从分割结果数组头部弹出第1个块表达式,调用Sizzle函数执行完整的查找、过滤,得到上下文数组set,
同“从右向左”的第2,3步
3. 从分割结果数组头部弹出第2个块表达式,调用位置选择器posProcess,set为上下文,并将返回的结果作为下一个块表达式的上下文
如果遇到块间关系符,再取一个块表达式
以下是位置选择器posProcess的执行过程:
位置选择器posProcess将块表达式中的伪类全部提取出来,存入伪类变量later,并删除块表达式中匹配的伪类
以set中的每一个元素作为上下文,对剔除了伪类的块表达式,调用Sizzle函数执行完整的查找、过滤,并将将结果集合并,存储在tmpSet
对结果集tmpSet,调用过滤器Sizzle.filter,将伪类作为过滤表达式,进行过滤
done,一个块表达式执行完成!
4. 重复第3步,将剩余的块表达式从左向右挨个执行第3步的流程,将最后结果做为候选集set和映射集checkSet
每一个块表达式,都会执行一次位置选择器posProcess,每次的解析结果set作为下一个块表达式的上下文,不断缩小上下文范围(从左向右的核心思想是不断缩小上下文)
这就是为什么从右向左效率更高的原因:
从右向左只对最后一个块表达式做完整的查找、过滤,对其余块表达式都是过滤
但是在从左向右中,要将上一个块表达式解析结果中每一个元素作为上下文,对当前块表达式进行完整的查找、过滤
—– 从左向右完成 —–
5. 根据过滤后的映射集checkSet,从候选集set中挑选最终的结果集,在映射集checkSet中
6. 如果存在并列表达式,重复1~5,并将得到的最终结果集合并、排序、去重,返回最终结果集
7. 如果存在多个上下文,对每个上下文重复1~6
8. 将从多个上下文找到的结果集合并、去重,返回最终结果集
第1、5、6、7、8与从右向左完全一样,详细分析请参考Sizzle的工作原理,可以把从左向右和从右向左的流程,放在一起对照研究。
感兴趣的同学可以把这些流程和分析,对照Sizzle的源码进行理解,只有自己真正读过度过才能体会其中的思想和技巧。