CSS选择器的执行效率已经改变了(增强)
像Dave Hyatt的Writing Efficient CSS这样优秀的文章已经帮助开发者们掌握了基本的选择器匹配优化原理. 我们从Steve Souders等大牛那里学到, 选择器是从右到左进行匹配的. 有的选择器匹配方式比较复杂所以应尽量避免使用. 比如说,后代选择器的匹配速度就比较慢,尤其是最右侧的选择器匹配了页面中大量元素的时候. 这些知识在早些年是很有用的. 但随着时间的发展, 感谢Antti Koivisto的努力, 很多选择器的效率问题我们已经无须过于担心了.
Antti致力于Webkit内核的改进工作. 他最近对CSS选择器的匹配机制进行了重要优化. 这些工作已经完成. 他表示:"我认为网页开发者已经无须对选择器进行优化了,那是浏览器引擎开发工程师的工作."
听起来很棒. 我喜欢以对文档结构更有意义的方式来使用选择器,选择器匹配效率优化这方面交给浏览器渲染引擎就好. 那么Antti到底对渲染引擎作了哪些优化呢? 事实上他对webkit渲染引擎的选择器匹配机制进行了多方面的优化. 我们来看看其中最主要的4项:
快速路径(Fast Path)
样式共享使得浏览器允许样式列表中的元素与之前的相同元素重复相同的样式而不是重复计算渲染.
例如:
foo
bar
如果浏览器内核已经计算好第一个
标签的样式,它就无须再次计算第二个
标签的样式. 这个简单但智慧的改进减少了浏览器的大量工作.
现在,我们都知道选择器是从右到左进行匹配的,所以最右侧的选择器十分重要. 规则散列将样式表以最右选择器为基准进行分组,例如下面的样式表将会被分成3组:
a {}
div p {}
div p.legal {}
#sidebar a {}
#sidebar p {}
|a|p|p.legal|
|-|-|-|
|a {}|div p {}|div p.legal {}|
|-|-|-|
|#sidebar a {}|#sidebar p {}|
当浏览器运用规则散列时,它无须分别解析整个样式表中的单个选择器,而是对范围小得多的可能存在匹配的分组进行解析. 规则散列也是个小巧简单却能大幅减少对单个HTML元素进行解析的改进.
祖先过滤器有一点复杂,它会对选择器匹配的可能性进行计算. 因此,当涉及的元素不需要与祖先相匹配时.祖先过滤器可以迅速排除相关规则. 于是,它检测后代选择器和子选择器并且基于class,ID,tag进行匹配.在以前,后代选择器是需要被特别在意的,它需要在各个祖先节点中循环以进行匹配,而布隆过滤器可以拯救这个问题.
布隆过滤器是测试特定选择器是否在某一集合中的数据结构. 看起来和选择器匹配很相似不是吗? 布隆过滤器会检测一条CSS规则是不是匹配当前正在测试的元素的CSS规则的子集. 关于布隆过滤器很棒的一点是,正误识是有可能的,负误识不是. 就是说,如果布隆过滤器指出某选择器没有匹配当前元素,浏览器会停止查询并前进至下一个选择器. 这样可以节省大量时间. 另一方面, 布隆过滤器指出当前当前选择器是匹配的, 浏览器会执行常规匹配策略直至100%确定匹配为止. 复杂的样式表会导致更多的正误识,所以建议为你的样式表保持合理的长度.
祖先过滤器加快了后代选择器和子选择器的匹配速度,它也可以用来将其他较慢的选择器划分为很小的子树,然后浏览器只有很少的几率需要处理那些低效率的选择器.
快速路径使用非递归、完全内联的循环重新实现了更多通用匹配逻辑. 它被用来匹配包含以下任意一种组合的选择器:
1.后代选择器,子选择器,向下派生选择器
2.标签选择器,id选择器,class选择器和属性构成的选择器
快速路径提升了大量关系选择器的性能. 事实上,选择器的总体效能被提升了25%,其中后代选择器和子选择的效能被提升了2倍,另外它也被用于querySelectorAll()方法的样式匹配上.
如果这么多选择器效能都被提升了,有哪些仍然是比较缓慢的呢?
Antti说,直接和间接的后代关系选择器仍然速度缓慢. 不管怎样,祖先过滤器和规则散列可以降低它们的影响因为它们很少被匹配. 他也提到Webkit还有很大空间来提升伪类和伪元素的解析效率. 但不论如何他们的解析速度比Javascript操作DOM快得多. 事实上,尽管仍有提升空间,他说:
"从样式匹配的角度说,适度使用一切选择器会表现得刚刚好."
我喜欢听他这么说. 总之如果我们把样式表控制在正常大小,并且合理使用各种选择器,就无须强迫自己迎合过去的选择器优化标准. 感谢Antti.
想知道更多? 点击Paul Irish’s presentation on CSS performance.
原作者Nicole Sullivan