为了让你的 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 始终有用于这些选择器的快捷方式,这些快捷方式会在可能的情况下使用 getElementByID
、getElementsByTagName
和 getElementsByClassName
。这没有改变(除了为元素根 ID 选择器添加了快捷方式),这些仍然是最快的选择器。任何其他选择器都将通过 querySelectorAll
(如果可用)运行,或者通过编译器运行。
querySelectorAll
和 matchesSelector
通过最新的重写,querySelectorAll 和 matchesSelector 的代码路径比以前更好,并且性能出色。
有些人问为什么我们还需要 Sizzle,因为现代浏览器有 querySelectorAll
和 matchesSelector
,并且接受各种 CSS3 选择器。问题在于,每个浏览器(不仅仅是 IE)在这些方法中都存在一些 bug。选择器引擎必须预先知道这些 bug 是什么,并在这些方法返回不正确的结果之前绕过它们(虽然不是所有引擎都这样做)。Sizzle 现在涵盖了这一点。
此外,querySelectorAll
和 matchesSelector
不知道如何处理 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 产生任何影响,因为它会维护对该方法的内部引用。
获取代码!
该代码现在可以在 jQuery 和 Sizzle 的 git 版本中获取。预计 jQuery 1.8 将在本月某个时候发布。专门针对 Sizzle 的问题可以在 GitHub 上提交,并且像往常一样,任何与 jQuery 整体相关的问题都可以提交到我们的 bug 跟踪器。自己试一试,如果遇到任何问题,请告诉我们。祝你玩得愉快,祝独立日快乐!