- jQuery从入门到精通(微课精编版)
- 前端科技
- 1366字
- 2021-04-02 00:05:44
3.5 选择过滤
本节将结合下面示例继续分析当完成词法分组之后Sizzle的操作。
HTML DOM结构:
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对象,来记录选择器相关的属性及操作。它有以下属性:
在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只有三种可靠的兼容用法。
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"]"。
分组之后,需要使用浏览器提供的API实现匹配,所以Expr.find就是最终的实现接口。
第1步,首先确定从右到左的顺序进行匹配,但是右边第一个是"[type="checkbox"]",Expr.find不认识这种选择器,所以只能往前继续找。
type: "TAG" value: "input"
第2步,这种标签Expr.find能匹配到,所以就会直接调用以下代码:
由于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()函数的源代码如下:
这个过程比较复杂,简单总结一下:
第1步,按照从右到左原则取出最后一个token,如[type="checkbox"]。
第2步,过滤类型 如果type是>、+、~、空格四种关系选择器中的一种,则跳过,继续过滤。
第3步,直到匹配到ID、CLASS、TAG中的一种,因为这样才能通过浏览器的接口获取元素。
第4步,此时seed种子合集中就有值了,这样把匹配的范围缩小到一个很小的范围。
第5步,如果匹配的seed合集有多个,需要进一步的过滤,修正选择器selector: "div > p+div.sub [type= "checkbox"]"。
第6步,完成选择过滤之后,跳到编译函数阶段。