浅谈Sizzle的“编译原理”

网络推广 2025-04-24 14:41www.168986.cn网络推广竞价

Sizzle,作为jQuery背后的DOM选择器引擎,其速度被誉为业界领先。它是由jQuery的创始人John Resig开发的一个独立、全新的选择器引擎,自jQuery 1.3版本后开始独立存在,作为一个开源项目,它不依赖于任何库。即使你不使用jQuery,也可以单独使用Sizzle,或者将其集成到其他框架如Mool、Dojo、YUI等。

在准备关于jQuery的分享PPT时,同事们对jQuery的选择器实现方法和其与其他框架的查询速度对比表示出了浓厚的兴趣。Sizzle之所以具有如此高效的运行速度,与其独特的实现原理息息相关。

让我们了解一下Sizzle的选择器。选择器格式对于熟悉jQuery的同学来说非常熟悉,例如:tagid.class > a:first。这个选择器语句看似简单,但从左至右的执行过程中涉及层层深入过滤查找匹配的dom元素。关键在于,Sizzle必须能够处理查询语句的各种可能组合和排列,以适应复杂的选择需求。

Sizzle的源码结构错综复杂,但我们可以先抛开外围的包裹,关注其核心部分。我认为整个实现中最核心的第一个方法是tokenize函数,位于源码的第1052行。

tokenize函数的作用是对传入的selector进行序列化操作。当parseOnly参数为false时,函数只进行序列化而不返回结果,序列化的结果会被缓存起来以备后用。这个函数的作用类似于“编译原理”中的词法分析,根据selector里的逗号、空格和关系选择符进行“分词”,得到一个二维数组。这个数组的第一维被称作groups,是根据逗号分隔的。

紧接着我们看到了Expr的定义,位于源码的第405行。Expr是一个包含所有选择符的对象,每个选择符对应一个方法定义。这些选择符包括“ID”、“TAG”、“CLASS”、“ATTR”、“CHILD”和“PSEUDO”等。

经过“分词”处理后,Sizzle会通过Expr中的方法定义来执行具体的查询或过滤操作。这里我们不得不提到我认为很核心的第二个方法——matcherFromTokens函数,位于源码的第1293行。这个函数的作用是通过tokenize方法得到的tokens生成匹配程序。

Sizzle的实现原理可以概括为:首先对selector进行“分词”处理,然后通过生成的匹配程序进行实际的查询和过滤操作。这个过程涉及到多个核心方法,包括tokenize、matcherFromTokens等。限于篇幅,这里只是简单分享了Sizzle的实现原理,详细的源码需要更深入的研究和。经过深入研究,我对Sizzle的选择器引擎的工作流程有了更深入的理解。下面,我将以生动的语言和丰富的文体,阐述我所领悟的内容。

在Sizzle中,matcherFromTokens方法扮演了核心角色,它像一座桥梁,连接了selector的“分词”和Expr中定义的匹配方法。可以说,无论选择符如何排列组合,都能通过这个方法找到匹配的路径。Sizzle的巧妙之处在于,它没有直接对应每个“分词”结果和Expr中的方法进行匹配,而是通过组合,形成强大的匹配方法,一步到位执行。这其中的关键在于第三个方法——superMatcher。

在源代码的第1350行,我们看到了superMatcher方法的定义。实际上,它并非直接定义的方法,而是通过matcherFromGroupMatchers方法生成的。这个方法根据传入的参数,确定查询的起始范围,可能是从seed中直接查询过滤,也可能在context或其父节点范围内进行查询。如果不从seed开始,那么它会首先执行Expr.find["TAG"]方法,得到一个elems集合。然后,对elems中的每个元素应用预先生成的matcher方法进行匹配,如果匹配成功,则将该元素添加到结果集中。

值得注意的是,matcher方法返回的结果都是布尔值。当我们回到第405行查看Expr中的filter方法时,发现它们也返回布尔值。这包括PSEUDO伪类方法也是一样。这似乎与最初的设想有些不同,它并不是一层层往下查,而是有点倒回去向上做匹配、过滤的味道。在Expr中,只有find和preFilter返回的是集合。

尽管带着一些疑问,但Sizzle的“编译原理”已经得到了清晰的解释。它通过逐个匹配、过滤的方式来得到结果集,这种方式有其独特的优点。

接下来,我们不得不提到Sizzle的入口——第220行的Sizzle函数。这个函数在开始阶段会检查是否可以使用简单的方法(如ID、TAG、CLASS选择符)来直接获取元素。如果这些方法都用不上,就会进入select方法。在select方法中,会对selector进行“分词”操作,然后根据选择符的type先使用find方法查找结果集。find操作按照选择符的顺序从左到右缩小结果集范围。如果所有选择符都能执行find操作,那么就直接返回结果。否则,就会进入前面提到的“编译”执行过滤的流程。

关于Sizzle的高效选择器实现,之前的诸多疑问现在都可以找到明确的答案。在反向匹配过滤的过程中,其查找范围已经通过层层过滤缩小到最小集合,这使得反向匹配过滤的方法对于伪类等选择符能够高效运作。不得不说,Sizzle的运作机制确实令人赞叹。

那么,为何Sizzle如此高效呢?这得益于其独特的工作流程。Sizzle总是优先使用最高效的原生方法进行处理。当涉及到选择器实现时,Sizzle会在执行前判断当前浏览器是否支持原生的querySelectorAll方法。如果支持,Sizzle会优先选择这种方法,因为浏览器原生支持的方法在处理效率上通常超过Sizzle自身的JavaScript实现。这一策略确保了Sizzle在执行时能够保持高效率。

在面临较为复杂的选择问题时,Sizzle同样展现出其智慧。它首先尝试利用原生方法如getElementById、getElementsByTag、getElementsByClassName等进行查询,以缩小待选范围。只有在更复杂的情况下,Sizzle才会转向之前我们介绍的“编译原理”,对待选范围的元素进行逐个匹配筛选。虽然相比前面的方法,“编译”环节的效率可能会稍低一些,但Sizzle通过优化,尽量减少使用这些方法,并通过各种手段让处理的结果集尽可能简单和缩小,从而提高效率。

更令人惊叹的是,Sizzle在执行过程中还采用了我们之前为解释流程而暂时忽略的缓存机制。在“编译”入口的源代码1535行,Sizzle会调用其核心方法superMatcher。进一步跟踪我们会发现,在1466行的pile方法会将根据selector生成的匹配函数缓存起来。不仅如此,在1052行的tokenize方法也会将selector的分词结果缓存起来。这意味着,当我们执行过一次Sizzle (selector)方法后,下次再调用时,无需再次经历耗时的“编译”过程,直接利用之前的缓存即可。在我看来,“编译”的最大好处之一就在于便于缓存,这里的“编译”可以理解为是生成预处理的函数并存储起来以备后用。

至此,我希望已经能够解答之前同学们关于选择器实现原理和执行效率问题的疑问。本文的分析结论基于Sizzle版本的源码,文中提到的代码行号也以此版本为准。由于时间仓促,分析可能存在不周到之处,请予以谅解。对于仍有疑问的同学,欢迎线下继续交流。

以上就是本文的全部内容,希望大家喜欢。

上一篇:php运用memcache的完整实例 下一篇:没有了

Copyright © 2016-2025 www.168986.cn 狼蚁网络 版权所有 Power by