chaojidan

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

统计

第十二课:Sizzle引擎详解

这篇博客难度太大,跟前端开发其实没什么关系,如果你想成为大牛,那就去了解下吧。如果你还不想,那可以忽略,毕竟面试官也不会问到这里来,因为他也不太懂。呵呵。

Sizzle引擎是jQuery的选择器,它大部分操作都是从右到左进行选择,特殊选择符会从左到右。用户输入$("div"),$("div p.class"),$("div [attr=val] :checked")等各种复杂的选择符,它都能选择到用户想要取到的元素节点。

Sizzle的整体结构如下:

(1)Sizzle主函数,里面包含选择符的切割,内部循环调用主查找函数,主过滤函数,最后是去重过滤。

(2)其他辅助函数,如 uniqueSort, matches ,matchesSelector。

(3)Sizzle.find主查找函数

(4)Sizzle.filter主过滤函数

(5)Sizzle.selectors 包含各种匹配用的正则,过滤用的正则,分解用的正则,预处理函数,过滤函数。

(6)根据浏览器的特征设计makeArray,sortOrder,contains等方法。

(7)根据浏览器的特征重写Sizzle.selectors中的部分查找函数,过滤函数,查找次序。

(8)若浏览器支持querySelectorAll,那么用它重写Sizzle,将原来的Sizzle作为后备方案包裹在新的Sizzle里面。

(9)其他辅助函数,如:isXML,posProcess。

Sizzle.find主查找函数和Sizzle.filter过滤函数实现原理:

对js原生的4大查找函数,getElementById(针对id),getElementsByName(针对name),getElementsByTagName(针对标签名tagName,比如div,p),getElementsByClassName(针对class),进行一层封装,浏览器支持的话,就返回数组或者NodeList,不支持的,就返回undefined。

这里需要讲一下种子集,

种子集就是通过最右边的选择器组得到的元素集合。比如:"div.aaa span.bbb",最右边的选择器组就是"span.bbb",这时引擎会根据浏览器的支持情况选择getElementsByTagName(span)或getElementsByClassName(bbb)得到一组元素,然后再通过class(bbb)或tagName(span)进行过滤,这时得到的集合就是种子集。种子集是分两步筛选出来的,首先,通过Sizzle.find得到一个大体的结果,然后通过Sizzle.filter过滤。那我们是先取span,还是.bbb呢?这里有一个准则,要确保我们后面的映射集(当我们取得种子集后,会将种子集复制一份,这就是映射集)最小。为了达到此目的,这里有一个优化,原生选择器的调用顺序被放在一个Sizzle.selectors.order的数组中,对于低版本浏览器,其顺序为id,name,tagName,对于支持getElementsByClassName的浏览器,顺序为id,class,name,tagName。因为id只返回一个元素,class与样式相关,不是每个元素都有这个类名的,name属性使用到的几率比较少,而tagName排除的元素比较少。所以Sizzle.find就会根据Sizzle.selectors.order数组,依次调用正则,从最右的选择器中切下需要的部分,找到粗糙的节点集合。(针对"span.bbb",id调用正则时,找不到,然后class,调用正则,找到.bbb,因此就调用getElementsByClassName(bbb)得到一组数据,最后通过Sizzle.filter过滤取到的数据,过滤条件是tagName(span))

映射集,

当我们取得种子集后,会将种子集复制一份,这就是映射集。种子集是由一个选择器组选出来的,这时如果选择符不为空(前面是"div.aaa"),必然往左就是关系选择器(父亲,兄弟,后代),关系选择器会让引擎去选取其兄长或父亲,把这些元素置换到映射集对等的位置上(个数不变,因此映射集和种子集的数量总是相当)。然后到下一个选择器组时("div.aaa"),就是过滤操作了。主过滤函数Sizzle.filter会调用Sizzle.selectors下的N个过滤函数对这些元素进行检测,将不符合的元素替换为false。因此到最后要去重排时,映射集是一个包含布尔值与元素节点的数组。

下面就是根据浏览器的特征进行优化:

IE6,7下getElementById有bug。需要重写。

IE6-IE8下,Array.prototype.slice.call无法切割NodeList。需要重写makeArray。jQuery中直接用循环,把类数组转化成数组。

IE6-IE8下,getElementsByTagName("*"),会混杂注释节点。

这里大家可能会提出现在有些浏览器支持querySelectorAll方法,这是原生的,可以用来选择元素。

在Sizzle中,当浏览器支持querySelectorAll方法时,会重写Sizzle。但是在重写时,会根据不同情况提出各种提速方案:

(1)getElementById还是比querySelectorAll速度快,因为getElementById只返回一个元素,而且内部做了缓存,但是querySelectorAll会返回拥有这个id值的多个元素,尽管页面id一般是唯一的,但如果出现了多个同样id的情况下,getElementById还是只返回一个元素,而querySelectorAll会返回多个。

(2)getElementsByTagName内部也使用了缓存,而且返回的是NodeList对象,querySelectorAll返回的是一个StaticNodeList对象,前面是动态的,后面是静态的。区别在于:document.getElementsByTagName("div") == document.getElementsByTagName("div"),返回真,document.querySelectorAll("div") == document.querySelectorAll("div"),返回false.返回true的,意味着它们拿到的同是cache引用。返回false意味着每次返回都是不一样的object。数据表明:创建一个动态的NodeList对象比创建一个静态的StaticNodeList对象快90%.

 

加油!

 

posted on   chaojidan  阅读(1152)  评论(7编辑  收藏  举报

编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
点击右上角即可分享
微信分享提示