Web性能优化最佳实践:JavaScript&CSS篇

Yahoo!的Exceptional Performance团队为改善Web性能带来最佳实践。他们为此进行了 一系列的实验、开发了各种工具、写了大量的文章和博客并在各种会议上参与探讨。最佳实践的核心就是旨在提高网站性能。

Excetional Performance 团队总结出了一系列可以提高网站速度的方法。可以分为 7 大类 34 条。包括内容、服务器、cookie、CSS、JavaScript、图片、移动应用等七部分。

本文为CSSJavascript部分:

除此之外,JavaScript 和 CSS 也是我们页面中经常用到的内容,对它们的优化也提高网 站性能的重要方面:

CSS

  1. 把样式表置于顶部

  2. 避免使用CSS表达式(Expression)

  3. 使用外部JavaScript和CSS

  4. 削减JavaScript和CSS

  5. 代替@import

  6. 避免使用滤镜

JavaScript

  1. 把脚本置于页面底部

  2. 使用外部JavaScript和CSS

削减JavaScriptCSS 和 剔除重复脚本(参考3、4)

  1. 减少DOM访问

  2. 开发智能事件处理程序

最佳实践

1.把样式表置于顶部

在研究Yahoo!的性能表现时,我们发现把样式表放到文档的<head />内部似乎会加快页 面的下载速度。这是因为把样式表放到<head />内会使页面有步骤的加载显示。

注重性能的前端服务器往往希望页面有秩序地加载。同时,我们也希望浏览器把已经接收到内容尽可能显示出来。这对于拥有较多内容的页面和网速较慢的用户来说特别重要。

向用户返回可视化的反馈,比如进程指针,已经有了较好的研究并形成了正式文档。在我们的研究中HTML页面就是进程指针。当浏览器有序地加载文件头、导航栏、顶部的logo等对于等待页面加载的用户来说都可以作为可视化的反馈。这从整体上改善了用户体验。

把样式表放在文档底部的问题是在包括Internet Explorer在内的很多浏览器中这会中 止内容的有序呈现。浏览器中止呈现是为了避免样式改变引起的页面元素重绘。用户不 得不面对一个空白页面。

HTML规范清楚指出样式表要放包含在页面的<head />区域内:”和<a />不同,<link /> 只能出现在文档的<head />区域内,尽管它可以多次使用它”。无论是引起白屏还是出 现没有样式化的内容都不值得去尝试。最好的方案就是按照HTML规范在文档<head />内加载你的样式表。

2.避免使用CSS表达式(Expression)

CSS表达式是动态设置CSS属性的强大(但危险)方法。Internet Explorer从第5个版 本开始支持CSS表达式。下面的例子中,使用CSS表达式可以实现隔一个小时切换一次背景颜色:

1
background-color: expression( (new Date()).getHours()%2 ? "#B8D4FF" : "#F08A00″ );

如上所示,expression中使用了JavaScript表达式。CSS属性根据JavaScript表达式的 计算结果来设置。expression方法在其它浏览器中不起作用,因此在跨浏览器的设计中

单独针对Internet Explorer设置时会比较有用。 表达式的问题就在于它的计算频率要比我们想象的多。不仅仅是在页面显示和缩放时,就是在页面滚动、乃至移动鼠标时都会要重新计算一次。给CSS表达式增加一个计数器可以跟踪表达式的计算频率。在页面中随便移动鼠标都可以轻松达到 10000 次以上的计算量。

一个减少CSS表达式计算次数的方法就是使用一次性的表达式,它在第一次运行时将结 果赋给指定的样式属性,并用这个属性来代替CSS表达式。如果样式属性必须在页面周 期内动态地改变,使用事件句柄来代替CSS表达式是一个可行办法。如果必须使用CSS表达式,一定要记住它们要计算成千上万次并且可能会对你页面的性能产生影响。

3.使用外部JavaScript和CSS

很多性能规则都是关于如何处理外部文件的。但是,在你采取这些措施前你可能会问到 一个更基本的问题:JavaScript和CSS是应该放在外部文件中呢还是把它们放在页面本 身之内呢?

在实际应用中使用外部文件可以提高页面速度,因为JavaScript和CSS文件都能在浏览 器中产生缓存。内置在HTML文档中的JavaScript和CSS则会在每次请求中随HTML文档重 新下载。这虽然减少了HTTP请求的次数,却增加了HTML文档的大小。从另一方面来说, 如果外部文件中的JavaScript和CSS被浏览器缓存,在没有增加HTTP请求次数的同时可 以减少HTML文档的大小。

关键问题是,外部JavaScript和CSS文件缓存的频率和请求HTML文档的次数有关。虽然 有一定的难度,但是仍然有一些指标可以一测量它。如果一个会话中用户会浏览你网站 中的多个页面,并且这些页面中会重复使用相同的脚本和样式表,缓存外部文件就会带 来更大的益处。

许多网站没有功能建立这些指标。对于这些网站来说,最好的坚决方法就是把 JavaScript和CSS作为外部文件引用。比较适合使用内置代码的例外就是网站的主页, 如Yahoo!主页和My Yahoo!。主页在一次会话中拥有较少(可能只有一次)的浏览量,你可以发现内置JavaScript和CSS对于终端用户来说会加快响应时间。

对于拥有较大浏览量的首页来说,有一种技术可以平衡内置代码带来的HTTP请求减少与 通过使用外部文件进行缓存带来的好处。其中一个就是在首页中内置JavaScript和CSS, 但是在页面下载完成后动态下载外部文件,在子页面中使用到这些文件时,它们已经缓存到浏览器了。

4.削减JavaScript和CSS

精简是指从去除代码不必要的字符减少文件大小从而节省下载时间。消减代码时,所有 的注释、不需要的空白字符(空格、换行、tab缩进)等都要去掉。在JavaScript中, 由于需要下载的文件体积变小了从而节省了响应时间。精简JavaScript中目前用到的最 广泛的两个工具是JSMin和YUI Compressor。YUI Compressor还可用于精简CSS。

混淆是另外一种可用于源代码优化的方法。这种方法要比精简复杂一些并且在混淆的过 程更易产生问题。在对美国前 10 大网站的调查中发现,精简也可以缩小原来代码体积 的 21%,而混淆可以达到 25%。尽管混淆法可以更好地缩减代码,但是对于JavaScript 来说精简的风险更小。

除消减外部的脚本和样式表文件外,<script><style>代码块也可以并且应该进行消 减。即使你用Gzip压缩过脚本和样式表,精简这些文件仍然可以节省 5%以上的空间。由于JavaScript和CSS的功能和体积的增加,消减代码将会获得益处。

5.用<link>代替@import

前面的最佳实现中提到 CSS 应该放置在顶端以利于有序加载呈现。

在 IE 中,页面底部@import 和使用<link>作用是一样的,因此最好不要使用它。

6.避免使用滤镜

IE 独有属性 AlphaImageLoader 用于修正 7.0 以下版本中显示 PNG 图片的半透明效果。 这个滤镜的问题在于浏览器加载图片时它会终止内容的呈现并且冻结浏览器。在每一个 元素(不仅仅是图片)它都会运算一次,增加了内存开支,因此它的问题是多方面的。 完全避免使用 AlphaImageLoader 的最好方法就是使用 PNG8 格式来代替,这种格式能在 IE 中很好地工作。如果你确实需要使用 AlphaImageLoader,请使用下划线_filter 又使之对 IE7 以上版本的用户无效。

7.把脚本置于页面底部

脚本带来的问题就是它阻止了页面的平行下载。HTTP/1.1 规范建议,浏览器每个主机 名的并行下载内容不超过两个。如果你的图片放在多个主机名上,你可以在每个并行下 载中同时下载 2 个以上的文件。但是当下载脚本时,浏览器就不会同时下载其它文件了, 即便是主机名不相同。

在某些情况下把脚本移到页面底部可能不太容易。比如说,如果脚本中使用了 document.write来插入页面内容,它就不能被往下移动了。这里可能还会有作用域的问 题。很多情况下,都会遇到这方面的问题。 一个经常用到的替代方法就是使用延迟脚本。DEFER属性表明脚本中没有包含 document.write,它告诉浏览器继续显示。不幸的是,Firefox并不支持DEFER属性。在 Internet Explorer中,脚本可能会被延迟但效果也不会像我们所期望的那样。如果脚

本可以被延迟,那么它就可以移到页面的底部。这会让你的页面加载的快一点。

8.剔除重复脚本

在同一个页面中重复引用 JavaScript 文件会影响页面的性能。你可能会认为这种情况

并不多见。对于美国前 10 大网站的调查显示其中有两家存在重复引用脚本的情况。有 两种主要因素导致一个脚本被重复引用的奇怪现象发生:团队规模和脚本数量。如果真 的存在这种情况,重复脚本会引起不必要的 HTTP 请求和无用的 JavaScript 运算,这降 低了网站性能。

Internet Explorer 中会产生不必要的 HTTP 请求,而在 Firefox 却不会。在 Internet Explorer 中,如果一个脚本被引用两次而且它又不可缓存,它就会在页面加载过程中产 生两次 HTTP 请求。即时脚本可以缓存,当用户重载页面时也会产生额外的 HTTP 请求。

除增加额外的 HTTP 请求外,多次运算脚本也会浪费时间。在 Internet ExplorerFirefox 中不管脚本是否可缓存,它们都存在重复运算 JavaScript 的问题。 一个避免偶尔发生的两次引用同一脚本的方法是在模板中使用脚本管理模块引用脚本。 在 HTML 页面中使用<script />标签引用脚本的最常见方法就是:

1
<script type="text/javascript" src="menu_1.0.17.js"></script>

在 PHP 中可以通过创建名为 insertScript 的方法来替代:

1
<?php insertScript("menu.js") ?>

为了防止多次重复引用脚本,这个方法中还应该使用其它机制来处理脚本,如检查所属 目录和为脚本文件名中增加版本号以用于 Expire 文件头等。

9.减少 DOM 访问

使用 JavaScript 访问 DOM 元素比较慢,因此为了获得更多的应该页面,应该做到:

  • 缓存已经访问过的有关元素

  • 线下更新完节点之后再将它们添加到文档树中

  • 避免使用 JavaScript 来修改页面布局

10.开发智能事件处理程序

有时候我们会感觉到页面反应迟钝,这是因为DOM树元素中附加了过多的事件句柄并且 些事件句病被频繁地触发。这就是为什么说使用event delegation(事件代理)是一种 好方法了。如果你在一个div中有 10 个按钮,你只需要在div上附加一次事件句柄就可 以了,而不用去为每一个按钮增加一个句柄。事件冒泡时你可以捕捉到事件并判断出是 哪个事件发出的。

你同样也不用为了操作DOM树而等待onload事件的发生。你需要做的就是等待树结构中 你要访问的元素出现。你也不用等待所有图像都加载完毕。 你可能会希望用DOMContentLoaded事件来代替onload,但是在所有浏览器都支持它之前 你可使用YUI 事件应用程序中的onAvailable方法。