最近一直在看Google Page Speed中介绍的Web Performance Best Practices以及Yahoo!的
Best Practices for Speeding Up Your Web Site,并且浏览了很多其他叙述有关WEB性能优化的文章后有一点感悟:当新技术新思想引进国内的时候,国外已经发展相对比较或者绝对成熟、研究有一定深度而且最主要的是理念已经被广为传播开来并为大家所熟知。而联想到国内当前的技术环境和理念创新方面,还是有待提高的。下面是我在浏览了众多有关WEB性能优化方面的论述后的一个总结,因为内容比较多,而且翻译总结时间比较长点,所以初步打算是逐步逐步共享一下自己的心得,后续的将会陆陆续续推出,说的有不正确的地方,欢迎指正~
因为总结是写在word文档里的,所以就顺便保存了一份pdf格式的文件,需要的童鞋可以自行下载:《WEB性能优化系列一》。
- Serve resources from a consistent URL:使用一个确定的URL来引用资源
- Serve scaled images:衡量图片的大小缩放
- Optimize images:优化图片
- 拆分初始化负载
- Minify HTML
- Minify CSS
- Minify JavaScript
- Remove unused CSS:删除无用的CSS
- Enable compression:允许压缩
- Minimize request size:最小化request请求头信息的大小
- Serve static content from a cookieless domain:减小静态资源的cookie的大小
- Optimize the order of styles and scripts:优化js和css文件的加载顺序
1. Serve resources from a consistent URL:使用一个确定的URL来引用资源
这个主要是避免不必要的资源重复下载和DNS查询。这种情况主要是当同一个资源(尤其是图片)在几个页面或者几个站点之间重复使用的时候,特别是公司内的各个站点,比如:a.example.com和b.example.com中同时引用了example.png这个图片,所以现在就要把这个图片放在a或者b域名下,而不能既放在a又放在b,这样会造成不必要的重复下载同一个图片,而且也增加了DNS的查询时间,造成性能问题。
同时还有一点需要指出的是:相对URL和绝对URL的问题。如果是在同一个站点内,/images/example.gif和www.example.com/images/exampl.gif是一样的,站内页面之间可以使用相对地址来引用图片,从而减少页面的大小。但是在站点之间的时候,就会有一个问题:图片所在的站点使用相对URL/images/example.gif来引用资源,而另外站点通过www.example.com/images/example.gif来引用的时候,IE和FF下都能够很好的处理,无论在哪一个站点访问了这个图片,在另外一个站点就可以在浏览器的缓存里读取图片,可是在Chrome下却不会,即使是一个相同的URL页面,不同的Tab下浏览都会重新下载资源。测试页面:http://www.ilovejs.net/lab/domain.html,http://www.webairness.com/apps/domain.html。
因此:将想要共享使用的资源(图片、CSS,JS等等)存放到一个唯一的站点里,其他站点都引用这个唯一的URL。
2. Serve scaled images:衡量图片的大小缩放
我们都知道页面中的图片最好能明确的通过HTML标签属性或者CSS来声明它实际的尺寸,而不要强制声明的尺寸跟图片实际尺寸不符合。但是有种情况是:在一个页面中需要显示一个图片的好几个不同的尺寸,而不想下载几个尺寸不同的图片。比如一个250×250的图片,需要在页面中显示一个缩略图是10×10尺寸的,这种场景经常在一个Tab组件中见到。Google Page Speed提到,如果图片的实际尺寸跟页面中几个尺寸中至少一个尺寸相符,并且是最大的那个,比如这里的是250×250,那么这对性能会有所提高。
对于上面提到的情况:到底是使用一个图片并在页面中强制声明缩放图片尺寸,还是加载几个尺寸不同的图片,从而在页面中显示不同尺寸但是内容相同的图片?我写了两个测试页面:http://www.ilovejs.net/lab/scale-img/test1.html,http://www.ilovejs.net/lab/scale-img/test2.html。
从对上面两个页面测试中发现:当时第一次浏览空缓存的时候,两个页面的渲染速度没什么差别,而总的加载时间是有缩放图片的页面快;当有缓存的时候,这就排除了HTTP请求数的影响,此时页面的渲染速度明显是几个尺寸不同的图片快。因此:这就需要一个衡量的事情。
附:在Google Page Speed中关于这个有一句En文不明白意思“However, if you serve an image that is larger than the dimensions used in all of the markup instances, you are sending unnecessary bytes over the wire.”希望懂的人指明一二~
3. Optimize images:优化图片
优化图片方面主要是利用好三种图片格式:PNG(又分为PNG8,PNG24)、GIF、JPG/JPEG。这三种格式各有用途,而总得来说:PNG用在Sprites背景图片、修饰页面的icon以及渐变图片中;GIF虽然也可以用在一些背景图片,视情况而定,主要就是因为GIF图片格式相对其他两种来说文件大小会小很多,适合用于一些清晰度要求不高的图片中;JPG/JPEG格式的文件大小一般都是最大的,因为它的色素高,适合展示相片之类对清晰度要求高的图片。
除了选择好了图片格式之外,还得利用工具来压缩图片,对于JPG/JPEG格式的图片,可以使用jqegtran和jpegoptim这两个工具;对于PNG,则可以使用OptiPNG和PNGOUT。
同时,在HTML编写方面,需要明确的指定图片的尺寸(显式声明width、height属性),避免浏览器不必要的渲染性能消耗。
4. 拆分初始化负载
随页面一起加载的Javascript中,有些函数是用于在加载页面的同时就起作用的,但是还有一部分的代码是在页面加载的过程中不会执行或者用不到的,如果让这部分代码连同需要执行的函数代码随页面一起加载的话,将会阻塞后续资源的加载,造成更多的时间延迟。所以我们需要将需要立即执行的代码和在页面onload之后才有可能出发的代码拆分开来,而这个拆分的平衡点就是:那些需要在页面的onload事件触发前需要执行的以及哪些是在onload之后才有可能执行的。 将那部分不会立即执行的代码通过onload事件来动态的加载,减小页面加载的总时间。
5. Minify HTML
Minify HTML文档的内容(包括行内脚本和行内样式),这样就可以减小页面的大小,更快的加载HTML页面、渲染和Layout文档。
Minify HTML文档跟缩小JS、CSS一样会得到非常多的好处:减小网络延迟、增进压缩、更快的加载和渲染。而且HTML经常会包含行内脚本和行内样式,压缩它们也都非常有必要。举个简单的例子:行内脚本使用script标签来引入,页面内只需要写script,而不需要写language和type属性,style标签也一样,不需要写明type属性,浏览器发现这些没写明的话,会采取默认的执行方式。
同时对于HTML标签的写法,也有几点建议:
- 对于属性用单引号还是双引号的问题。建议统一使用单引号或者双引号,或者在DOCTYPE允许的情况下,省略掉。
- 对于属性的顺序问题。比如a标签,应该把href属性放在最前面来声明,其他属性根据字符顺序排序来声明:<a href=”#” alt=””title=””>link</a>。
- 在标签于标签之间,去掉不必要的空格和\t\r\n等等,例如可以看看http://www.ilovejs.net 站点的HTML源码。
- 对于CSS的key-value的编写方式建议按照字母顺序a-z来组织。
6. Minify CSS
Minify CSS代码(移除不必要的空格、注释、换行符等等)可以节省字节数、加快下载、渲染和执行时间。
有几个工具可以用来实现压缩CSS:YUI Compressor、 cssmin.js。
同时,对于Minify CSS在编写代码方面尽量保持简写和归类的方式,这样一来容易维护,二来在性能上也会有所提高。比如:
简写:background:url(example.png) no-repeat 0 0;
归类:#test1,#test2,…#testN{background:url(example.png) no-repeat;}
7. Minify JavaScript
Minify Javascript代码(移除不必要的空格、注释、换行符等等)可以节省字节数、加快下载、渲染和执行时间。
Minify Javascript之后有三个好处:
- 如果有些行内脚本和外部脚本不想要缓存的时候,文件越小网络延迟加载的时间就越短。
- Minify Javascript之后可以更加增进外部脚本代码的压缩以及HTML内的行内脚本。
- 更小的文件可以更快的被浏览器加载和执行。
有几个工具可以用来压缩Javascript代码:JSMin,YUI Compressor,Closure Compiler,Dojo Shrinksafe。
8. Remove unused CSS:删除无用的CSS
删除或者推迟加载一些当前文档用不到的样式规则,可以避免不必要的下载字节数以至于让浏览器更快的开始渲染页面。
在浏览器开始渲染页面之前,它必须要下载和解析全部的用于布局页面的样式表。即时一个外部的样式表已经存在于缓存中,页面渲染也都会被阻塞直到浏览器从缓存中加载了全部的样式表。另外,一旦样式表被加载下来,浏览器的CSS引擎就会执行每一条样式规则,从而确定每一条声明的样式规则对当前文档是否得到应用。但现实中的情况是很多网站从始至终在网站的每一个页面中都使用同一个样式表,而不管样式表中有些规则是否会被当前文档用到。
所以,最好的方式就是最小化由样式表加载和解析所造成的延迟时间,而解决方案就是移除或者延迟加载那些不被当前文档使用到的样式规则。
对此,Google Page Speed做如下建议:
- 在行内样式中删除掉任何不为当前页面使用的样式规则。
- 如果你的站点中在几个页面中都引用了同一个样式文件,考虑下将样式文件拆分为更小的文件来为特定的页面特定的样式。
- 如果一些样式规则在页面加载的时候不需要使用,最好将他们放置到另外一个文件里,在页面的onload事件里加载它。
- 如果使用Javascript来动态生成样式规则时,确保那些会生成当前页面不需要的的样式规则的Javascript函数不会被执行。
上面提到的四点对于清除无用的CSS是比较良好的解决方案和建议。口碑网现在加载页面的样式表同样使用了Minify的方式,因为头部和底部是全部页面都通用的,所以页面统一都加载了base.css和header.css。但是口碑网站中包括了许多中不同样式的头和底部,base库里也会有非常多的样式不能在全部的页面中都使用上,所以就造成了在当前页面非常多的无用的CSS规则,这一来影响了CSS文件的加载,二来影响了CSS引擎的解析。
所以,更理想的方式是将Minify的方式更加的颗粒化,将base.css和header.css更加的细分,真正做到按需加载。
9. Enable compression:允许压缩
用Gzip或者deflate来压缩资源可以减少大部分的网络传输数据量,这点是毋庸置疑的。可以压缩HTML文档内容、CSS文件、Javascript文件等等来使得页面加载速度更快。
当前主流浏览器都支持使用gzip/deflate来压缩HTML文档、CSS和Javascript文件,从而使得从服务端发送回来的数据量比原始大小减少了许多(通常在50%上下),这是非常可观的。Gzip压缩的原理非常简单,就是将指定的文件类型在服务器端打包压缩,在发送回浏览器解压,这个过程是服务器和浏览器会自动处理的,我们只需要正常的使用普通的方式链入CSS、Javascript文件即可。
但是我们也知道,这个压缩和解压的过程,是需要消耗CPU和内存的,所以gzip压缩一般只在压缩大文件的时候才能显示出它的优势。Google Page Speed经过测试发现:当使用gzip来压缩一个大小在150到1000 bytes的文件时,实际上会使他们更大,体现不出gzip的优势了。所以尽量保持在只对1000 bytes大小以上的资源进行gzip压缩。
同时还有一点需要指出的是:不要对图片或者其他的二进制的文件(比如:PDF,video,ppt等等)进行gzip压缩,因为它们已经进行过压缩了。如果对它们使用了gzip压缩之后,不仅不会带来应有的好处,还会实际上使它们更大。如果要压缩图片,可以查看上面的:Optimize images:优化图片。
10. Minimize request size:最小化request请求头信息的大小
尽量保持request请求头信息的短小能确保HTTP的request请求可以通过发送一个数据包就可以完成请求信息的传输。
理想的情况下,一个HTTP请求尽量不要超过一个数据包来发送到服务器。当前大部分的在使用的网络对一个数据包大小的限制是在1500 kytes左右,所以当你限制每个请求的头信息的大小在1500 bytes以内,你就可以减少发送请求头的时间总开销。而HTTP请求头信息一般包括:
- Cookies。如果请求一些资源必须要发送cookie的时候,尽量保持cookie的大小尽量的小,来确保HTTP请求头信息的总大小在限制的范围内。因此,发送的每一个cookie尽量保持在1000 bytes以内。建议是每个域名下发送的全部的Cookies的平均值在400 bytes以下,将没用的或者是重复的Cookies给删除掉。
- 浏览器自动设置的信息。许多头信息都是由用户代理自动地设置的,这部分是无法控制的。
- 请求的URL(GET或者Host头)。如果URL设置了很多参数的话,这会无疑的增加了HTTP请求头总体的大小。所以尽量保持URL的大小越小越好,建议是至少是在几百bytes之内。
- Referer URL。有时候请求头中会包含referer头指定的URL,这个URL跟第三点就有相同的要求了。
因此,尽量保持HTTP请求头信息总大小在限制的范围内,对提高页面加载速度,还是有效果的。
11. Serve static content from a cookieless domain:减小静态资源的cookie的大小
通常如果在一个域名下(比如www.example.com)的HTML页面在发送请求的时候设置了cookie,那么在页面中的一些静态资源的请求头中也会发送这些cookie,而服务器对于静态资源发送的cookie是毫无作用的,反而是徒增了HTTP请求头的大小。所以,确保静态资源的HTTP请求头中不发送这些cookie来减小HTTP请求头信息的大小。
解决这个问题的方案是:创建一个子域名或者购买一个新域名来映射静态文件,跟当前域名隔离开来。
比如:你的域名是www.example.com,你就可以创建一个子域名static.example.com来映射静态文件。但是,如果你是将cookie设置到最高级的example.com域名下,那么子域名下的静态资源也都会发送cookie,这个时候,你就不得不花钱购买一个新的域名来映射这些静态资源。
同时,移除静态资源的cookie的另外一个好处是避免一些代理对静态资源拒绝缓存的情况,因为一些代理会拒绝缓存一些HTTP请求头中包含cookie信息的静态资源。所以从这方面出发,避免静态资源的HTTP请求头中包含cookie信息,益处是颇大的。
还有一点需要说明的是,对于Javascript文件,如果它一定要放置在head里并需要在页面中即时加载,最好将这个Javascript文件放置到跟HTML文档同一个域下,因为反正Javascript文件的加载都会阻塞页面其他资源的加载和阻塞页面的渲染,所以就没有必要将这个Javascript文件放置到其他域下,增加DNS查找的延迟时间。
12. Optimize the order of styles and scripts:优化js和css文件的加载顺序
正确对外部的样式表文件和脚本文件或者行内脚本进行排列加载,会得到更好的并行下载数和加快浏览器渲染页面。
这个主要是因为Javascript代码可能会改变页面的内容和布局,所以浏览器在遇到script标签的时候,会延迟渲染页面任何内容,直到脚本下载并执行完毕。而还有一点就是加载脚本会阻塞后续任何资源的加载,这个是最致命的。但是另一方面,在页面的加载队列里,如果在脚本文件的前面已经存在有另外的文件需要加载,那么脚本文件将会跟这些文件并行加载。
所以这就有几种情况:
- 当js文件穿插在css文件中来加载。这样的后果就是每到加载js文件的时候,就会阻塞后续的js或者css文件的加载,这无疑就没有利用好浏览器的并行加载的机制。
- 当js文件都放在css文件的最末尾来加载。因为css文件的加载不会阻塞后续资源的加载,而且还可以跟其他资源并行加载,所以前面的样式表和样式表后的第一个js文件可以并行加载。这比第一点就快速多了,具体图示可以浏览《Optimize the order of styles and scripts》。
这里还有一种情况需要说明的是:当行内脚本紧跟在样式表后面的时候,将会阻塞后续资源的加载(这里我提一点:是当script标签紧跟在样式表link标签后面的时候,也将会阻塞后续资源的加载,测试页面:http://www.ilovejs.net/lab/script-after-styylesheet.html)。当行内脚本紧跟在样式表后面的时候,浏览器要在样式表完全下载之后才开始执行行内脚本,如果行内脚本又需要执行比较长的时间,这可能还会更糟。这是因为行内脚本有可能含有依赖于样式表中样式的代码,比如HTML5中的getElementsByClassName方法。浏览器按顺序下载样式表和执行行内脚本是为了保证一致的结果。
====================== 待续 ======================
Updates:
以下是瓶子对第一点“Serve scaled images:衡量图片的大小缩放”的一个翻译,觉得比较符合英文愿意:
有时候,你或许想用不同尺寸显示同一张图片,因此你会传输同一张图片,然后用html或者css去缩放它。例如,你想把一个250*250的图片缩放到10*10大小的图片,通常不是倾向于强制用户下载2个独立的文件,你会用标记去重定义小图片的大小。这意味着实际图片的尺寸应该至少与页面的一个图片(尺寸最大的那个)相匹配,本例是指那个250*250的。然而,如果你传送了一个比所有标记的图片尺寸都大的图片,那么你就是在发送不必要的字节。你应该使用一个图片编辑器来缩放图片到页面需要的最大尺寸的那个标记的图片,同时确保你在页面中指定了这些尺寸。
However, if you serve an image that is larger than the dimensions used in all of the markup instances, you are sending unnecessary bytes over the wire.
这几句话的直译:然而,如果你使用的图片尺寸大于所有标记的实例的尺寸,那么你就是在发送不必要的字节。
因为没有上下文,我的理解是:如果你有一个图片,无论在什么地方用它,都强制使用了小于他实际尺寸的外观,那么就意味着你浪费了很多字节的传输。因为图像压缩的时候,是将图片分割成几个块(通常是4个),然后字节的排列按照每个块一个像素的方式编排在一起。也就是说,如果你的图片是缩放的,那就意味着在图片的末尾一些字节是没有必要传送的,浪费了带宽和服务器空间。
起初我也都是这样想的,可是在那两个测试页面中,有一个原来是所有图片都缩放了的,可是在firebug下查看,并没有增加网络的传输量。所以现在我有这样的一个理解:在图片原本大小的基础上缩小了尺寸,这样如果跟生成该图片缩小尺寸后的另一个图片来比较,当然还是会前者大,这样在网络传输的时候自然就认为是传输了不必要的字节了。
瓶子想得太深了,我的理解是,大图总是比小图的体积大,如果页面中需要使用小图,就直接加载小图,而通过css强制成小尺寸,请求加载的还是大图。
如果页面中重用同一个图的不同尺寸的话,还是只用一个大图,然后强制成不同尺寸的好,因为相对于http请求和图片下载,感觉浏览器渲染图片尺寸的影响是可以忽略的。而且,缓存是靠不住的,这个地方需要权衡的话,我倾向于加快首次访问的速度。
4.对于CSS的key-value的编写方式建议按照字母顺序a-z来组织。
————————————————-
这个好像不太具有可行性,感觉编写和维护更困难了,一般都是按照特定的顺序去编写,比如position-float-display-width-height-margin-padding-color-background…..等等,这样的写法更符合人的直觉,而不是给计算机看的。
5.简写:background:url(example.png) no-repeat 0 0;
————————————————
你这么变态的人,后面的0 0就不要写了,默认就是0 0;
我只是一个匆匆过客,感谢作者能耐心翻译此文~
你关于图片的想法跟我的比较类似。
当是第一次加载的时候,使用了缩放的方式比使用不同尺寸的图片要加载的快,可是当有缓存的时候,渲染成本会大幅度提升,体现的比较明显了。
本文罗列的很多都只是建议,当然还得从可读性和编写习惯方面去权衡。
默认是0 0以前还不知道,哈哈……
6月6号俺来围观一下,欢迎回访~
引用: WEB性能优化系列二 | Web is dancing
However, if you serve an image that is larger than the dimensions used in all of the markup instances, you are sending unnecessary bytes over the wire. 加载的图片比使用的图片尺寸大的话,会增加下载的大小.
那两个测试实例:
第一个下载的大小肯定比第二个大;
第一个http请求数肯定比第二个少;
考虑下载大小和http请求数,最后还是合并成一张图片合适
现在考虑的是当一个页面中用到了同一个图片的几个不同尺寸的情况。这个时候,就需要考虑是用一个图片来缩放所有尺寸还是下载几个不同尺寸但是图片内容相同的图片。
如果是背景的话,sprites固然是好。
把图片合并到一起,只能用于背景吗?不是的,定位好后,overflow:hidden;就可以使用.
不错的思路!!受教了,非常感谢redky!!
那就得将图片设置为绝对定位了,而且可能还要设置图片父元素的position为relative和overflow:hidden.