![jQuery从入门到精通(微课精编版)](https://wfqqreader-1252317822.image.myqcloud.com/cover/735/26542735/b_26542735.jpg)
3.5 选择过滤
本节将结合下面示例继续分析当完成词法分组之后Sizzle的操作。
HTML DOM结构:
![](https://epubservercos.yuewen.com/A91516/15056702905210506/epubprivate/OEBPS/Images/Figure-P82_40697.jpg?sign=1738914384-qLPUvCX1JtnGepJSdLDWTNumcMqwAD0S-0-d56b218f9b49d7da5bf8cf14e32a498a)
CSS选择器:
div > p+div.sub input[type="checkbox"]
JavaScript脚本:
<script> window.onload = function () { console.log(Sizzle('div > p+div.sub input[type="checkbox"]')) } </script>
下面按常规思维逻辑来描述主要任务。
第1步,选择div元素的所有子元素p。
第2步,选择紧邻p元素后的所有div元素,且class="sub"。
第3步,选择div.sub元素内所有input元素,且type="checkbox"。
在jQuery 3.2.1中,针对高级浏览器会自动使用querySelectorAll处理所有CSS选择器,不再使用低效的原始方法。为了深入学习Sizzle引擎,本节主要讲解在低版本中是如何实现的,其中伪类选择器、XML处理等在后文讲解,本节暂不涉及这方面的处理。
首先,读者需要了解下面知识点。
CSS选择器的位置关系。
CSS选择器基本实现接口。
CSS选择器从右到左匹配原则。
3.5.1 位置关系
在HTML文档中,所有节点之间都存在如下几种关系。
祖先和后代
父亲和儿子
相邻兄弟
同级兄弟
在CSS选择器里分别对应的标识符是空格、>、+、~。
其实还有一种特殊关系—div.sub,中间没有空格表示选取一个class为sub的div节点,相当于限定关系。
在Sizzle中,专门定义了一个Expr对象,来记录选择器相关的属性及操作。它有以下属性:
![](https://epubservercos.yuewen.com/A91516/15056702905210506/epubprivate/OEBPS/Images/Figure-P83_40707.jpg?sign=1738914384-8YS0HZmZkOAsELog1tDkwI7vqKsQIGEs-0-cecf531d2714528e153e7ad62105c33d)
在Expr.relative属性中定义了一个first属性,用来标识两个节点的“紧密”程度。例如,父子关系和相邻兄弟关系就是紧密的。在创建位置匹配器时,会根据first属性来匹配合适的节点。
3.5.2 实现接口
除了querySelector和querySelectorAll,HTML DOM提供了4个API接口。
getElementById:上下文只能是HTML文档。
getElementsByName:上下文只能是HTML文档。
getElementsByTagName:上下文可以是HTML文档、XML文档、元素节点。
getElementsByClassName:上下文可以是HTML文档、元素节点。提示,IE 8-不支持。
所以Sizzle只有三种可靠的兼容用法。
![](https://epubservercos.yuewen.com/A91516/15056702905210506/epubprivate/OEBPS/Images/Figure-P84_40710.jpg?sign=1738914384-adtiiFRAlC0cXX8lRgZcitxl11DU8Qru-0-0d6fc3a2aae307c70c8242b7d59bed28)
3.5.3 匹配原则
CSS选择器遵循从右到左的匹配原则。以下面CSS选择器为例:
div > p+div.sub input[type="checkbox"]
通过词法分析器tokenize分解后,对应的规则,即分解的每个小块如下:
type: "TAG" value: "div" matches ... type: ">" value: " > " type: "TAG" value: "p" matches ... type: "+" value: "+" type: "TAG" value: "div" matches ... type: "CLASS" value: ".sub" matches ... type: " " value: " " type: "TAG" value: "input" matches ... type: "ATTR" value: "[type="checkbox"]" matches ...
除关系选择器外,其余有语意的标签都对应分析出matches。例如,最后一个属性选择器分支"[type= "checkbox"]"。
![](https://epubservercos.yuewen.com/A91516/15056702905210506/epubprivate/OEBPS/Images/Figure-P85_43864.jpg?sign=1738914384-nElpqWIdzV4pllb0AlNr0HXy3CKQ1bED-0-612d9002851346fb779ed0a406ea8e46)
分组之后,需要使用浏览器提供的API实现匹配,所以Expr.find就是最终的实现接口。
第1步,首先确定从右到左的顺序进行匹配,但是右边第一个是"[type="checkbox"]",Expr.find不认识这种选择器,所以只能往前继续找。
type: "TAG" value: "input"
第2步,这种标签Expr.find能匹配到,所以就会直接调用以下代码:
![](https://epubservercos.yuewen.com/A91516/15056702905210506/epubprivate/OEBPS/Images/Figure-P85_43866.jpg?sign=1738914384-qSLkbA6LTw6yhosX2bjoEyh2fNGI76P3-0-c6dc11b80f6b2cfce0c613029925f4b3)
由于getElementsByTagName方法返回的是一个合集,所以Sizzle在这里引入了seed(种子合集),搜索器搜到符合条件的标签,都放入这个初始集合seed中。
第3步,完成匹配之后,就不再继续往下匹配了,开始进行整理:重组CSS选择器,剔掉已经用于处理的TAG标签—input。这时CSS选择器缩减为:
selector: "div > p+div.sub [type="checkbox"]"
如果直接剔除后,selector为空,就证明满足匹配要求,直接返回结果。
第4步,如果selector不为空,则开始进行过滤操作。这里能够使用的对象包括seed集、通过tokenize分析组成match合集。
删除input之后,CSS选择器变成:
selector: "div > p+div.sub [type="checkbox"]"
此时,send目标合集有两个最终元素。
第5步,下面开始使用select()函数快速从两个条件中找到目标元素。select()函数的源代码如下:
![](https://epubservercos.yuewen.com/A91516/15056702905210506/epubprivate/OEBPS/Images/Figure-P86_40820.jpg?sign=1738914384-WjGuMkoqP640A1ww8Iy9ureqStF4MgZe-0-3e57071e7b5c4b99bf63209c55a9d979)
![](https://epubservercos.yuewen.com/A91516/15056702905210506/epubprivate/OEBPS/Images/Figure-P87_13515.jpg?sign=1738914384-1RjdOS2B5NN0YYmjBCqzCEov9VvgPJzo-0-e47dd637157dd9913161ac16c8ca5e24)
![](https://epubservercos.yuewen.com/A91516/15056702905210506/epubprivate/OEBPS/Images/Figure-P88_40821.jpg?sign=1738914384-Ek0yD0rja8Qu2aO51euHHolQgaMFAMvS-0-7ca2a706e50dfd665aa4d39034496dad)
这个过程比较复杂,简单总结一下:
第1步,按照从右到左原则取出最后一个token,如[type="checkbox"]。
![](https://epubservercos.yuewen.com/A91516/15056702905210506/epubprivate/OEBPS/Images/Figure-P88_40823.jpg?sign=1738914384-3w6JMnqdao2BPwKsk31AWfJKvzADJSzx-0-13d17633dc86d0c4e7c885fbd3d09e8d)
第2步,过滤类型 如果type是>、+、~、空格四种关系选择器中的一种,则跳过,继续过滤。
第3步,直到匹配到ID、CLASS、TAG中的一种,因为这样才能通过浏览器的接口获取元素。
第4步,此时seed种子合集中就有值了,这样把匹配的范围缩小到一个很小的范围。
第5步,如果匹配的seed合集有多个,需要进一步的过滤,修正选择器selector: "div > p+div.sub [type= "checkbox"]"。
第6步,完成选择过滤之后,跳到编译函数阶段。