全新的 Sizzle

发布日期 作者

为了让你的 7 月 4 日更加火爆(别客气),jQuery 团队很高兴地宣布,jQuery 的 CSS 选择器引擎 Sizzle 已经比以往任何时候都更好、更快、更可靠!Sizzle 已经进行了实质性的重写,并将包含在 jQuery 1.8 版本中。

首先,我们要感谢 Diego Perini 指引我走向正确的方向,也要感谢 Samuel Lebeau 在 3 年前创建了一个名为 Bouncer 的项目,这是一个“用于 JavaScript 的快速自下而上的元素匹配器”。

jQuery 和 Sizzle 于 2006 年发布,大约在 Simon Willison 发布 getElementsBySelector 3 年后,这基本上为我们今天所有的选择器引擎奠定了基础。随着时间的推移,Sizzle 为了性能进行了多次重写,并且随着使用它的人数增加,越来越多的错误被修复。

在此期间,其他一些相当令人印象深刻的选择器引擎被引入,包括但不限于 NWMatcher(由 Diego 开发)、dojo.query、Slick、base2、qwery 和 YUI。虽然它们都有自己的优势,但 NWMatcher 和 Dojo 尤其引人注目。虽然它们都不是在所有选择中都最快,但它们在几乎所有选择器中都始终保持快速。我的目标是为 Sizzle 达到同样的性能水平,保留 John 和错误团队多年来收集的所有边缘案例错误修复,并涵盖队列中的更多错误,或由其他引擎修复的错误。现在我可以自信地说,这个目标已经实现了。

虽然我不会说 Sizzle 完全没有错误,或者它在所有情况下都是最快的,但它的可靠性和 性能提升 非常有竞争力。 http://jsfiddle.net/timmywil/s7rN4/ 是一个用于快速观察一些选择器引擎在几个选择器上的差异的简单测试(应该在打开控制台的浏览器中运行)。

发生了什么变化

以下是 Sizzle 在 jQuery 1.7.2 和 jQuery 1.8 中主要代码差异的简化列表。

一个编译后的选择器函数

选择器解析器将一个选择器编译成一个包含每个选择器部分函数的函数。这意味着对于任何给定的选择器(不包括位置(POS)选择器,如 :first:eq(3)),只需要检查一次可能的一组元素。这主要是在哪里实现主要的 速度提升 和更高的稳定性。

此外,Sizzle 保持最近编译的函数的缓存。缓存有最大大小(可以调整,但有默认值),因此在使用大量不同的选择器时不会出现内存不足错误。

注意:这不会对简单选择器(仅 ID、仅 TAG、仅 CLASS)产生影响,因为 Sizzle 对这些选择器有快捷方式,这些快捷方式尽可能地委托给 getElementByIDgetElementsByTagNamegetElementsByClassName。这没有改变(除了添加对元素根 ID 选择器的快捷方式),它们仍然是最快的选择器。任何其他选择器都将在 querySelectorAll 可用时使用 querySelectorAll,否则将通过编译器运行。

querySelectorAllmatchesSelector

通过这次最新的重写,querySelectorAllmatchesSelector 的代码路径比以前更好,并产生了优异的性能。

有些人问为什么我们还需要 Sizzle,因为现代浏览器拥有 querySelectorAllmatchesSelector,并且接受广泛的 CSS3 选择器。问题是每个浏览器(不仅仅是 IE)在这两种方法中都有几个错误。选择器引擎必须预先知道这些错误是什么,并在这些方法返回不正确的结果之前绕过它们(虽然不是所有选择器引擎都这样做)。Sizzle 现在已经解决了这个问题。

此外,querySelectorAllmatchesSelector 不知道如何处理 jQuery 选择器扩展,例如 [attr!=value]。任何时候你使用选择器扩展,Sizzle 都需要原生处理选择。

改进的选择器验证

验证选择器是一项棘手的任务。过于严格可能会很烦人,但过于灵活可能会产生意外的结果。过去,Sizzle 在不同的时间和不同的用例中既严格又灵活。最近的变化旨在尽可能地遵循 W3C 选择器规范,但也允许一些规范中没有的东西(例如,在 :not() 伪类中包含复杂选择器)。

具体来说,我们匹配所有必要的空格字符,包括换行符、制表符、回车符和换页符 (http://www.w3.org/TR/selectors/#whitespace),验证属性选择器中的标识符和运算符 (http://www.w3.org/TR/selectors/#attribute-selectors),并提供与规范匹配的字符编码 (http://www.w3.org/TR/css3-syntax/#characters)。

组合器(空格、~、>、+)

组合器可能非常复杂,但新策略以极大的优雅处理这些问题。在 jQuery 1.8 beta 版本(以及今年的 jQuery 大会上),我声称 Sizzle 改进了对组合器的支持。虽然准确性得到了提高,但我言之过早,幸运的是,这一点被我在 GitHub 上只知道为 Yaffle 的人指出了。显然,对于非常大且深的文档,原始的修订检查了如此多的元素,以至于对于具有多个组合器的选择器而言,会导致堆栈溢出。对于每个组合器,为了保持可能的匹配,所检查的元素数量呈指数级增长。这很糟糕。Sizzle 现在解决了这个问题,并获得了非常令人满意的结果。

可扩展性

虽然这次重写中 Sizzle 的大部分旧 API 没有改变(除了从私有 API 中删除现在不再必要的 Sizzle.filter 之外),但也有一些更改使 Sizzle 更加可扩展。扩展 Sizzle 最常见的做法是添加自定义伪选择器。现在,有了解析器编译函数的函数,你在创建自定义选择器时可以获得更多信息。例如,在 Sizzle 中,:not 伪选择器的实现与以下非常相似

// Using the createPseudo function tells the compiler
//   to pass the pseudo argument, context, and whether the current context is xml
//   to the function passed to createPseudo and trusts
//   that a function to be used for filtering will be returned.
// Note: the use of createPseudo is only necessary for creating custom
//   pseudo selectors with arguments.
Sizzle.selectors.pseudos.not =
    Sizzle.selectors.createPseudo(function( selector, context, isXml ) {
        var matcher = Sizzle.compile( selector, context, isXml );
	return function( elem ) {
		return !matcher( elem );
	};
    });

这是公共 API 中唯一与新解析器相关的重大更改,但我认为现在使用参数创建自定义伪类更加简洁。有关更多信息,请参阅 Sizzle 文档

也许你们中的一些人正在想,预编译自己的选择器会很不错。好吧,你可以做到。Sizzle.compile 被公开,因此你可以在选择器被使用之前对其进行缓存。虽然编译在没有缓存的情况下仍然非常快,但你可以在选择器运行之前确保跳过这一步。用你的选择器和上下文调用 compile

Sizzle.compile(“my>long>complicated:selector(poof)”, document);

它会被添加到缓存中。你甚至可以通过设置 Sizzle.selectors.cacheLength 来增加/减少缓存的大小。

注意:大多数用户不需要使用编译器,因为 Sizzle 会维护最近编译的选择器的缓存。覆盖 Sizzle.compile 不会对 Sizzle 产生任何影响,因为它维护了对该方法的内部引用。

获取代码!

代码现在可在 jQuerySizzle 的 git 版本中获取。预计 jQuery 1.8 将在本月某个时候发布。针对 Sizzle 的具体问题可以在 GitHub 上提交,而且一如既往,任何与 jQuery 整体相关的问题都可以提交到我们的 错误跟踪器。自己试一试,如果遇到任何问题,请告知我们。祝你使用愉快,并祝独立日快乐!

关于“全新的 Sizzle”的 12 个想法

  1. 只有我一个人在 jsperf.com 上看这些图表很费劲吗?这些条形图太小了,颜色很多,而且彼此相近。我花了好一阵子才弄明白两个紫色略微不同。

    话说回来,恭喜你改进。

  2. @Stifu:是啊,当运行的测试超过几个时,browserscope 很难阅读。这篇文章中的一些性能测试比其他测试更容易阅读,但运行这些测试可能会让你对结果有所了解。

  3. Alexander Makarov 说:

    根据测试,旧版 Sizzle 在最新的稳定版 Opera 中对“div”、“.unitTest”、“.test div:first-child” 的性能更好。

  4. @Alexander:好的。首先,仅包含标签(div)和仅包含类(unitTest)不受此重写的影響。其次,这取决于您查看的是哪个测试。如果您查看的是没有将 querySelectorAll 设为 null 的性能测试,则 Opera 中的差异微不足道(并且 1.8 现在检查 Opera 的 QSA 中的一个错误,而在 1.7.2 中未检查该错误)。如果您查看的是 http://jsperf.com/sizzle-1-7-2-vs-1-8/4,则绕过 querySelectorAll 会显示 Opera 12 中所有选择器都有所改进,这突出了解析器性能的提高。

  5. Alexander Makarov 说:

    我正在查看 http://jsperf.com/sizzle-1-7-2-vs-1-8。您说的是对的,性能下降的地方差异并不大,所以可能是额外修复的 bug 或简单的测量误差。修订版 4 的性能要好得多。

    这些“CSS 3 选择器测试”怎么样?这些方块对我来说都是黑白的,没有绿色或红色。圆圈没问题。

  6. Alexander Makarov 说:

    感谢您向我解释所有这些内容,以及新的更好的选择器引擎。如果我发现任何错误,我会直接在 github 问题中发布。

  7. @mathieu:任何选择器扩展都不会被 querySelectorAll 支持,这一点仍然是事实。但是,我不会说你应该避免使用它们。有些用例对性能更加敏感,例如对数百个表格单元格进行事件委派。只有在这些更敏感的用例中,我才开始担心将选择器扩展排除在选择器之外。大多数用例不需要进行这种微调。