# 选择器性能

工作的关系,我必须对性能做些检查。我们在应用上运行了一些工具希望找到性能瓶颈所在。Google Page Speed (opens new window)就是这样一个工具,它可以提供几条提升JavaScript和渲染性能的建议。在我给出这些建议之前,我们最好知道一点浏览器解析CSS的原理。

# CSS是如何被解析的

# 一个元素的样式是元素被创建时应用的

我们经常认为我们的页面是充满元素和内容的完整文档。但是,浏览器其实被设计用以处理流一样的文档。浏览器会从服务器中接受文档,然后在文档没有完全下载时就开始渲染。每个节点都在它们被接收到以后就被解析,然后渲染到视口中。

<!-- 一个HTML文档的例子 -->
<body>
   <div id="content">
      <div class="module intro">
         <p>Lorem Ipsum</p>
      </div>
      <div class="module">
         <p>Lorem Ipsum</p>
         <p>Lorem Ipsum</p>
         <p>Lorem Ipsum <span>Test</span></p>
      </div>
   </div>
</body>

浏览器从顶部开始解析,看到一个body元素。这时,它认为这个元素是空的,它不会解析任何其他东西。然后浏览器会确认计算得到的样式,然后将其应用到元素中。字体是什么?颜色是什么?行高是多少?当搞清这些之后,它就会将其绘制在屏幕上。

之后,浏览器会看到一个有ID的div元素,它还是会将其认为是空的,不会解析任何其他东西。之后,浏览器搞清楚样式是怎样的,然后div就会被绘制。浏览器会确认是否需要重新绘制body,它有没有变宽或者变高呢?(我怀疑还有其他属性会造成影响,但是宽度和高度应该是子元素对父元素造成影响的最主要因素)

这个过程会持续进行直到解析到文档底部。

查看Firefox的回流和重绘的可视化过程,可以访问http://youtu.be/ZTnIxIA5KGw (opens new window)

# CSS是从右往左解析的

为了确认一个CSS规则是否应用于某个元素,浏览器会将选择器从右往左解析。

如果你有一个像body div#content p { color: #003366; }这样的规则,那么对于每个渲染到页面中的每个元素,浏览器都会问是否有一个p元素。如果有的话,它会在DOM结构上找是否有一个带有contentID的div。如果它找到它想要的,它就会继续在DOM上找直到找到body

通过从右往左解析,浏览器可以确认一个规则是否应用于某个元素,并让其更快地渲染到视口中。而为了判断一个规则是否是高性能的,你需要计算出样式应用到某个元素时需要多少个节点被遍历。

# 哪些规则起了作用?

当元素被渲染到页面上,它需要确定应该应用哪些样式。现在让我们看下Google Page Speed建议 (opens new window)。有四个主要规则,它认为是低效的。

  • 使用后代选择器的规则。例如,#content h3
  • 使用孩子或者是兄弟选择器的规则。例如,#content > h3
  • 含有过于精确的选择器的规则。例如,div#contente > h3
  • :hover应用到非链接元素的规则。例如,div#content:hover

这些建议最关键的就是需要解析超过一个元素来确认样式是否应该被应用的选择器都是低效的。这意味着你只能在你的规则中使用单个选择器:一个类选择器,一个ID选择器,一个元素选择器或者是一个属性选择器。如果你接受这些建议的表面价值,那他们其实在建议你回到<p class="bodytext">这样的命名方法上。(如果你查看像谷歌搜索,谷歌邮箱这样的产品的CSS时,你会发现这些产品遵循这样的建议。)

# 限制自己,但是不要让自己窒息

对于其他人来说,我相信其实可以更加实际一点,在两个极端之间取得平衡。(一端是给所有元素添加类名,另一端是使用深度选择器规则在HTML和CSS之间创建紧密的联系)

我遵循三个简单的指导原则来限制元素被解析的数量:

  1. 使用孩子选择器
  2. 避免使用常用元素的标签选择器(tag selector)
  3. 使用类名作为最右侧的选择器

例如,如果我们知道页面中只会有十几个h3,那么.module h3就可能是OK的。不过,我们还是得确认:在DOM结构,各个h3层次有多深?它们是只有4级深(例如,html > body > #content > h3)还是有10级深(例如,html > body > #content > div > div > … > h3)?我是否可以通过使用孩子选择器来限制DOM遍历?如果我们使用.module > h3(IE6不行),并且知道我们页面上有12个h3,那就只有24个元素被遍历到。如果我使用.module div,并且页面上有900个div(我只是在雅虎邮箱里加载收件箱,就有903个div),那我将会进行数量庞大的遍历。一个简单的<div><div><div></div></div></div>(3层深)会有6次遍历,这是个阶乘,4层深会有24次遍历,5层深会有120次遍历。

尽管如此,这些简单的优化也可能是矫枉过正的。Steve Souders是性能测试的专家,他测试了CSS选择器对性能影响 (opens new window)发现最好和最差的测试用例也就相差50毫秒。也就说,其实我们只要考虑到选择器性能即可,不要浪费太多时间在上面