2010-6-12 Updates:既然我们通常说道Gzip压缩的效果如何的好(确实很好),那我们就来说一下Gzip压缩的原理吧:当服务器开启了Gzip压缩后,当从服务器加载一个文件的时候,就会将这个文件里相似的字符串临时地替换为规定形式的标记,之后在支持Gzip压缩的浏览器中替换回来并显示到页面上。如果文件里相似或者相同的字符串越多,压缩效果就越好。所以这个对HTML、JS、CSS文件压缩效果非常明显,因为它们的代码中有非常多相似的字符串可以压缩。为了证实这个说法,写了两个测试页面:《Gzip file1》,《Gzip file2》,第一个文件的原始大小为4K左右,第二个为10K左右,而压缩效果明显是第二个压缩的更尽致。
因此,利用Gzip的压缩原理,可以对文件的内容适当的做些手脚来使得压缩效果更加的尽致。
Updates:外联js文件的时候,给script标签带上async和defer属性
浏览器正常加载js文件的方式在性能上是比较糟糕的,阻塞了后续资源的加载和渲染,直到js文件加载并执行完毕。而async属性就顾名思义了,它使得js文件的加载和执行是异步的,不会阻塞后续资源的加载:《async加载Javascript测试》。当然了,要支持async的浏览器才行。
defer首先在IE下支持,最新版的FF等浏览器也相继支持了。它的主要作用是让js延迟加载,而不阻塞后续资源的加载和页面的渲染。这对于支持它的浏览器得到了一定程度的优化,而不支持的会忽略掉这个属性。
因此,建议同时加上async和defer属性,对大部分的浏览器都得到了优化,而不支持的浏览器也会按照默认的方式加载,何乐而不为呢。
昨天分享了些关于Web性能优化的几个方面《WEB性能优化系列一》,接下来分享下另外的一些优化方面的心得体会,算是续上一篇博文,欢迎指正~
因为本文的篇幅较小,就不提供链接目录了,提供PDF格式的文档下载:《WEB性能优化系列二》
1. Use efficient CSS selectors:使用高效的CSS选择符
关于高效的CSS选择符的叙述,很多书籍和网站很多博客都有相关的说明,尤其《Writing Efficient CSS for Use in the Mozilla UI》提出了很多的建议以及讲解了CSS引擎解析样式规则的原理。在那本《高性能网站建设进阶指南》中也独立出一章节来讲解高效的CSS选择符。
当浏览器开始下载HTML内容并解析HTML文档的时候,就会生成一个DOM节点树。而CSS选择符的匹配就是在这个节点树中查询符合条件的元素,并添加样式。当浏览器在解析CSS样式表的时候,它会解析样式表中的每一条样式规则,而不管最后样式规则是否会匹配到元素。
并且,CSS引擎是从右往左进行解析规则的,所以这里就有几点建议可以用来优化CSS选择符了:
- 避免最右端的选择符选择过多的DOM元素。比如全局选择符“*”,标记符(.hide-scrollbars * {…}),这些在CSS引擎解析该规则的时候最开始会匹配DOM节点树中太多的节点,这样再往左匹配的时候,造成匹配所用的时间就更长了。所以,右边的选择符匹配的元素越少越好。
- 对于子选择符或者说是邻近选择符(child-selector:ul > li > a)为什么低效,Google Page Speed做了如下解释:因为浏览器在解析一个选择符其中一步的时候,比如上面的a,首先它要确认它的父元素中存在li元素,同时还要判断li元素是否是a的直接父元素,这就需要两个判断操作,相比于后代选择符(ul li a)来说,它就多花费了一倍的性能消耗来执行每一步的匹配。
- 避免一些累赘的选择符。比如在class类中增加了标签(div.sidebar),ID也同样。当然了,如果你想要通过这种方式来增加选择符的权重,这个就另当别论了。
- 对于:hover选择符的选择。:hover选择符在IE7以上的版本的标准模式中都是良好的,但是一旦回退到怪癖模式,就跟IE6一个样了,只认超链接元素。所以总是声明标准的DOCTYPE,可以避免很多不必要的问题发生(推荐使用HTML5的声明方式)。
- 从CSS引擎的解析原理出发,选择符越简短越好,越明确越好,确保右边的规则不会选择过多的DOM元素。同时,可以的话选择class来代替后代选择符。
2. Specify a character set early:尽早的声明character
这点在《页面中charset的meta声明将如何影响页面加载性能》这篇文章中有详细的介绍,这里就不多说了。
3. Specify image dimensions:声明图片的尺寸
很多网站页面中对图片都没有明确的声明图片img的尺寸(即:设置img标签的height和width属性为图片的原始大小,或者在CSS中声明,不过最好还是在img标签中直接声明)。明确的在img标签中声明图片的尺寸可以加快浏览器对图片的渲染和布局,从而避免不必要的回流(reflow)和重新渲染(repaint)。
当浏览器布局页面的时候,它需要循环检查那些元素是可以置换的,比如图片。如果浏览器预先知道了一个可以置换的元素(比如图片)的尺寸,那么就可以使得一些不可置换的元素跟这个可置换元素正常的布局,即使图片还没有加载下来,浏览器就可以开始正常的渲染、布局整个页面。
但是,如果没有预先声明的尺寸,或者声明的尺寸跟元素的实际尺寸不符,那么一旦这些元素下载下来的时候,浏览器就需要一次回流(reflow)和重新渲染(repaint)的过程。
因此,为了避免不必要的回流和重新渲染的操作,预先声明图片的宽高,并且这个宽高需要跟图片的实际大小相符,其他可置换元素也一样的原理。
4. Put CSS in the document head:将CSS文件放到head里声明
将行内样式和CSS文件都放到head部位来声明,可以使得页面能及时的渲染,也能避免不必要的“白屏”和“无样式内容的闪烁”的问题的产生。
同时还有一点需要说明的是:当使用@import来加载外部样式表的时候,也可能会出现上面提到的“白屏”和“无样式内容的闪烁”的问题,因为采用@import加载样式表的时候,加载顺序是不定的,它有可能使得样式表在最后才加载进来。还有避免样式表后面紧跟script标签所声明的行内脚本。
5. Put Javascript in the document bottom:将脚本文件放到文档的末尾
这个主要是考虑到正常通过script标签加载Javascript文件的时候,会阻塞后面一切资源的加载。所以尽可能的将Javascript文件都放到文档的末尾来加载,就不会影响页面的渲染进程,但是它仍然对onload事件造成延迟,直到文件下载并执行完毕。《http://www.ilovejs.net/lab/loadjs/》这个页面展示了动态加载Javascript文件的方式,可以有效的解决这个延迟问题。
同时,Javascript文件和样式表文件要按照合理的顺序来加载,避免阻塞样式表加载的问题。
6. Avoid CSS expressions:避免使用CSS expression
关于css的expression,应用的是比较少的,但是不能说它没有被使用。Css的expression会明显的降低页面渲染的性能,避免使用它会改善IE用户的体验。
CSS expression是在IE5的时候加进IE中的,使用它可以使得Javascript可以通过一些事件动态的操作CSS中的某些属性,从而来改变document的某些属性。它在IE5到IE7版本之间受支持,IE8声称不再受支持,同时其他浏览器都不支持。
但是使用CSS expression会造成非常糟糕的性能问题。因为在一些普通细小的操作都可能使得expression重复执行,比如:窗口缩放、鼠标移动等等,反正是触发了reflow的因素都会触发expression,这对IE用户来说,是很恶心的性能问题,严重的阻碍用户跟页面的交互操作,恐怖的就是使得IE崩溃了。
7. Combine external Javascript and CSS:合并外部的Javascript和CSS文件
这个主要是从最小化HTTP请求这个优化准则来说的,将几个Javascript文件或者几个CSS文件合并成一个文件,再结合Gzip压缩,来优化资源的加载时间。可以浏览这篇文章的说明:《Javascript,CSS Minify》。
8. Avoid bad requests:避免请求无效的链接
当我们点击了一个超链接,而这里链接的目的地却是无效的,这个不仅造成了恶心的404状态吗,而且还严重影响了用户体验。通过返回404状态的时候都是比较消耗时间的,因为服务器需要查询整个站点是否有这个链接所执行的资源,到最终重定向到一个404页面。而用户等待了些时间最后得来的,却是一个404 Not Found页面,很恶心的事情。
因此,当一个站点越来越大,更新快的时候,适时的检查页面的链接是否失效并及时的修正这些无效的链接,这对提高用户体验以及避免页面加载一些无用的字节都是有用的。
9. Minimize redirects:最少化重定向的数量
重定向在某些情况下是必须的,将用户从一个页面重定向到另外一个页面。但是重定向会触发额外的HTTP请求和响应的操作,并且增加了往返时间的延迟,而这个额外的HTTP请求和响应就是301和302状态码了。从重定向的时候服务器会返回一个301或者302状态码来确定此次请求是重定向,之后再发送请求到服务器请求另外一个页面来加载页面内容和资源。
这里我需要说明一点的是我们平时都可能不太在意的细节—URL,我们知道有些URL中有斜杠“/”,而有些URL中不包含,比如:http://www.example.com/sport和http://www.example.com/sport/ ,这两个URL中的区别仅仅是最末尾的一个斜杠,但是请求这两个URL时,第一个链接会产生一个重定向,因为服务器不知道请求的这个是文件还是目录,当判断是目录的时候,就会在URL的末尾加上一个斜杠(这就造成重定向),之后就会查询该目录下的index文件名的文件来响应请求;而第二个链接就会使得服务器直接省略重定向的操作,请求该目录下的index类型的文件。
通过,还可以几点说明:如果需要重定向几个页面的话,比如A—B—C,但是如果能直接A—C重定向就能实现功能的话,就尽量减少重定向的次数。
通常我们从跟一个页面重定向到另外一个页面的方式是采用Javascript的localtion或者在head里通过meta的方式来实现,但是这样都会因重定向造成的性能问题。对于上面提到的URL方面不注意造成的重定向,有时候可以通过在服务器端rewrite的方式来解决。这就可以使得从一个URL映射到另外一个URL,这个操作是服务器自动完成的,不会出发重定向的问题。
2010-6-8 Updates:在其他牛人的PPT中收集的一些优化技巧
- 预加载资源。如果能预知用户下一步将会浏览哪个页面,就可以利用浏览器在当前页面的onload事件之后一点“空暇”时间来预加载一些资源(比如:图片、样式表、脚本等等),以期在下一个页面中会使用到。但是有一点必须要谨慎:预加载的资源不能影响当前页面的功能和效果,或者至少不能让用户察觉到。
- 避免把样式表的加载放置到body中。例如:如果head部分已经加载了一个样式表,这个样式表会解析并且渲染页面样式,如果body中又加载了样式表,这个样式表又重新渲染了页面的一些模块的样式,或者重定义了一些样式规则,就显得累赘了。如果把页面中所有的样式表都放到body中加载,则延迟了页面的加载时间。
- 加载外部脚本会阻塞页面的后续资源的加载,即使是不同域下的脚本也都是。
- 避免将cookie设置到最高级域下。比如将cookie设置到example.com下,这样子域也会在HTTP请求头中带有主域的cookie。
- 给永久的静态资源(图片、flash、样式、脚本)等添加Expires、Etag、Control-cache头来使得浏览器长久的缓存资源。使得重复浏览页面的时候从浏览器缓存读取资源。
- 优化DOM的一系列操作。DOM、HTMLCollection对象的操作在Javascript中是非常低效的。优化DOM中循环操作对性能提高最甚,比如使用利用局部变量、转HTMLCollection为数组、定时器、事件委托、文档碎片(document fragment)等等方式。
- 使用data:URL来链接base64编码的文件。这个方法尤其是在sprites图片背景或者其他一些下载文件的时候用到,但是由于IE6、7不支持data:URL,所以需对它们做特殊处理。本博客的sprites背景也使用了这种方式,具体的用法可以浏览《优化试试玩》一文。
通过这样的方式,可以减少HTTP请求,但是又得保证它的可缓存性。
- 在HTML中始终闭合标签,而不要依赖浏览器多出一个步骤来自动渲染闭合标签,况且IE下在缺失的闭合标签可能会导致布局错乱。