在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的源码进行理解,只有自己真正读过度过才能体会其中的思想和技巧。

发表评论

邮箱地址不会被公开。 必填项已用*标注