新的 Sizzle

发布于 作者

为了让你的 7 月 4 日更加“sizzlin”(欢迎),jQuery 团队很高兴地宣布,Sizzle,jQuery 的 CSS 选择器引擎,比以往任何时候都更加出色、快速和可靠!Sizzle 进行了重大重写,并将包含在 jQuery 1.8 版本中。

首先,应该感谢 Diego Perini 指引我前进的方向,以及 Samuel Lebeau 三年前创建了一个名为 Bouncer 的项目,它是一个“快速的自下而上的元素匹配器,用于 Javascript”。

jQuery 以及 Sizzle 于 2006 年发布,大约是在 Simon Willison 推出 getElementsBySelector 之后 3 年,该项目几乎为我们今天拥有的每一个选择器引擎奠定了基础。随着时间的推移,Sizzle 被重写了几次,以提高性能,并且随着使用它的人数增加,越来越多的错误被修复。

在这段时间里,还出现了其他非常出色的选择器引擎,包括但不限于 NWMatcher(由 Diego 创建)、dojo.query、Slick、base2、qwery 和 YUI。虽然它们都有自己的优势,但 NWMatcher 和 Dojo 尤其突出,作为典范的引擎。虽然没有一个引擎在所有选择上都最快,但它们在几乎所有选择上都始终保持快速。我的目标是为 Sizzle 达到相同的性能水平,保留 John 和 bug 团队多年来积累的所有边缘案例 bug 修复,并涵盖更多排队中的错误或其他引擎涵盖的错误。我现在可以自信地说,这个目标已经实现。

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

发生了什么变化

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

一个编译的选择器函数

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

此外,Sizzle 保持了最近编译的函数的缓存。缓存有一个最大大小(可以调整,但有默认值),这样你在使用很多不同的选择器时就不会遇到内存不足错误。

注意:这不会影响简单选择器(仅 ID、仅 TAG、仅 CLASS),因为 Sizzle 始终有用于这些选择器的快捷方式,这些快捷方式会在可能的情况下使用 getElementByIDgetElementsByTagNamegetElementsByClassName。这没有改变(除了为元素根 ID 选择器添加了快捷方式),这些仍然是最快的选择器。任何其他选择器都将通过 querySelectorAll(如果可用)运行,或者通过编译器运行。

querySelectorAllmatchesSelector

通过最新的重写,querySelectorAllmatchesSelector 的代码路径比以前更好,并且性能出色。

有些人问为什么我们还需要 Sizzle,因为现代浏览器有 querySelectorAllmatchesSelector,并且接受各种 CSS3 选择器。问题在于,每个浏览器(不仅仅是 IE)在这些方法中都存在一些 bug。选择器引擎必须预先知道这些 bug 是什么,并在这些方法返回不正确的结果之前绕过它们(虽然不是所有引擎都这样做)。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 并没有改变(除了删除现在不再需要的 Sizzle.filter 从私有 API 中),但有一些更改让 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 整体相关的问题都可以提交到我们的 bug 跟踪器。自己试一试,如果遇到任何问题,请告诉我们。祝你玩得愉快,祝独立日快乐!