1. 减少HTTP请求
一个典型的http请求报文大概是这样的:
GET /3.3.0/build/yui/yui-min.js
HTTP/1.1Host: yui-s.yahooapis.comPragma: no-cacheCache-Control: no-cache...
虽然也就几行文字,但是考虑到http协议里,对同一个域名同时发出的请求是受限的[1],如果请求太多,说不定它们会堵塞在队列中呢。哦,对了,忘记把cookie算上去了,每次请求的时候都会附带当前域下的cookie,有时候关这一项就有几百B呢。
解决方法:
文件打包使用CSS Sprites: 将小图标们合并成一幅大背景图,再通过恰当地设置
background-image
和background-position
来取出。根据你所用的语言和框架,一般都能找到相关的工具来完成这一任务。除了可以打包图片,还可以打包css文件和js文件。把多个相关js文件和css文件打包成单一的js文件或css文件,省下的http请求数量。这也可以交由工具去做。比如Flask可以使用Flask-Assets。内联图片可以用data: URL模式来内联图片。比如Github 404页面上的几幅图片:https://github.com/404
2. 使用CDN
据原文的数据,对于最终用户,80-90%的响应时间都消耗在下载页面的各种组件(js、css、flash等等)中。所以加速网站响应时间,就得加速各种静态资源的下载。要想让用户尽快下载到静态资源,根据物理法则,就要把它们放到离用户最近的地方。这时候,CDN就有用武之地了。
什么是CDN?
简单说,就是通过用户就近性(IP地址)和服务器负载的判断,CDN会让用户从离他们最近的内容服务器中下载所需的静态资源。据原文数据,Yahoo将静态文件迁徙到CDN之后,响应速度加快了20%以上。当然对于一般厂商而言,不可能在全国各地自建CDN机房,这时就需要购买第三方的CDN服务了。
3. 在响应报文添加Expire和Cache-Control
Expire和Cache-Control的介绍见这里:http://www.path8.net/tn/archives/2745
这是设置浏览器缓存用的。注意,如果你设置了较长时间的缓存,那么每次修改组件内容时,也需要一并修改组件名字,否则浏览器不会重新发起请求。这就是为什么我们看到的许多js和css文件都带着hash戳或者时间戳。
4. 用Gzip压缩组件
什么是Gzip?
从HTTP/1.1开始,如果客户端支持压缩,会在在HTTP请求中添加Accept-Encoding: gzip, deflate
,服务器就能够据此返回压缩后的数据。(并在响应报文中设定Content-Encoding: gzip
)压缩后的数据可以减少多达70%现在就打开你的浏览器的开发者工具,查看响应报文,你会看到你所浏览的网页是经过gzip压缩的。而且毫无解压上的延迟,对吧?你可以gzip一切,除了图片和pdf,因为这些文件一般都是压缩过了的,使用gzip甚至可能会增大文件大小。
5. 把css文件链接放在顶部
不解释,这是html的规范了。
Unlike A, it may only appear in the HEAD section of a document, although it may appear any number of times[2]
6. 把js文件链接放在底部
道理基本上是众人皆知。因为js文件下载后,就会被浏览器执行,同时页面的渲染会被阻塞掉。要知道,等到页面中链接的js文件、js文件中引用的其他js文件都执行完才渲染页面,用户可能已经不耐烦地按下F5了。
你也可以看下script元素的defer属性。
7. 避免使用css表达式
这种东西已经不存在了。
8. 外链js和css文件
第一条规则说到,我们应该尽量减少js和css文件个数来减少HTTP请求,那么减少到多少才是合适呢?取个极端情况,能不能完全把js和css代码内联到html文件中?
减少js和css文件数,需要注意一种情况。假如有些js和css文件经常改变,那么把它们合并在一起,会导致整个文件的改变,以及浏览器缓存的失效。所以更好的做法是,规划静态资源文件,把相对不变的合并在一起,把频繁变易的分隔开。
9. 减少DNS查找
每次访问互联网上的一个主机,假如没有命中DNS缓存,就会发起一次DNS查询,会消耗掉一定时间。如果把资源文件分布在不同的主机中,就会增加DNS缓存不命中次数,不过这又跟前面的几天规则相违背,所以还是需要权衡啊。当然如果贵司足够霸气,可以考虑下面这个方案:全局精确流量调度新思路-HttpDNS服务详解
10. 压缩js和css文件
11. 避免重定向
该用的时候还是得用。
12. 移除重复的script
13. 配置ETag
后端可以在响应报文中添加Etag这一项,那么当浏览器下次请求同样的资源时,会携带If-None-Match条目。假如Etag没有发生变化,服务器可以返回304 Not Modified
状态码,无须重新下载资源。
例如:
响应报文:
HTTP/1.1 200 OK
Last-Modified: Tue, 12 Dec 2006 03:03:59 GMT
ETag: "10c24bc-4ab-457e1c1f"
Content-Length: 12195
下一次的请求报文 :
GET /i/yahoo.gif HTTP/1.1
Host: us.yimg.com
If-Modified-Since: Tue, 12 Dec 2006 03:03:59 GMT
If-None-Match: "10c24bc-4ab-457e1c1f"
HTTP/1.1 304 Not Modified
这件事一般交由你用的服务器(比如Nginx)去做
14. 缓存Ajax
使用前面讲到的技术,如expire、cache-control、etag等,让浏览器将Ajax返回的结果也缓存起来。
15. 提前返回后端生成的内容
在后端的html页面完全渲染完之前,先返回部分内容。文中以PHP作为一个例子:
...<!-- css, js -->
</head>
<?php flush(); ?>
<body>
... <!-- content -->
调用php的flush
函数来“冲刷”后端缓冲区中的内容。貌似是先返回head中的内容,让浏览器加载css文件,具体得由懂php的人来解释下……
补充一个刚看到的,在Node中使用Bigpipe的例子:
// 原谅乱七八糟的缩进……
app.get('/profile', function(req, res){
var ep = new EventProxy();
res.write('...'});
res.write("<script type='text/template' id='tpl_articles'>
<%= articles %></script><script type='text/template' id='tpl_users'>
<%= users %></script><script> var bigpipe = new Bigpipe();
bigpipe.ready('articles', function(data){
$('#body').html(_.render($('#tpl_articles').html(), {articles: data}));
});
bigpipe.ready('users', function(data){
$('#body').html(_.render($('#tpl_users').html(), {user: data}));
});</script>");
ep.all('users', 'articles', function(users, articles){
res.end(); // 输出完整内容
});
ep.fail(function(err){
res.render('err', {msg: err.message});
});
db.getData('sql1', function(err, data){ // 获取用户数据
...
res.write('<script>bigpipe.set("users", ' + JSON.stringify(data) + ')</script>') // 插上这一段
ep.done('users');// 发出信号
});
db.getData('sql2), function(err, data){ // 获取文章数据
...
res.write('<script>bigpipe.set("articles", ' + JSON.stringify(data) + ')</script>')
ep.done('articles');
});
});
这里使用了EventProxy这个库,在渲染模板的同时,从数据库获取用户和获取文章,然后向模板的末尾插上对应的js脚本内容。
结果之后前端渲染的时候,就把这个数据渲染进去了。
16. 在Ajax请求中使用GET方法
因为在浏览器的实现中,GET方法耗时更短。
不过这得看业务逻辑的,对不对?
17. 延迟加载
如果可能,直到需要用时才加载某些图片、js文件……这方面有很多可用的库,比如echo.js
18. 预先加载
呃,看待事物果然不能太绝对……这里的预先加载,是说在浏览器空闲的时候,预先加载用户接下来要浏览的内容。文中举了Google首页中的css sprite作为例子。虽然首页用不上这个sprite,但是考虑到用户基本不会停留在Google首页,而且首页内容较少,于是预先加载了这个sprite
19. 减少DOM元素数量
过多的DOM元素会拖慢js执行的数目。特别是有些页面,使用div层层嵌套。原文强调尽量少用额外添加div的方式来实现某种效果,要考虑到html的语义。
运行document.getElementsByTagName('*').length
看下当前页面有多少个DOM元素,跟优秀的同类页面比较下。
20. 将不同的内容分到不同的域名下
理由见规则1
同时注意规则9的影响,不要分得太多。
21. 最小化iframe的数目
<iframe>
好处都有啥:
* 加载第三方内容
* 作为沙盒
* 并行下载脚本
* 加载那些通用的内容(但是又不打算改为单页应用)
<iframe>
的坏处呢:
* 花销
* 阻塞页面加载
* 语义丢失
22. 减少404
对静态资源的请求,避免返回404
23. 减少cookie大小
消除无用的cookie
尽量最小化cookie的大小
恰当地设置cookie的作用域,以免影响其他子域名
恰当地设置cookie的过期时间(如果不设置的话,一旦浏览器关闭,cookie就会失效。所以无关紧要的cookie就不要设置过期时间了)
24. 给静态资源分配一个无cookie的域名
一般情况下,请求静态资源是不需要带cookie的,所以把它们独立开来。参见规则1
25. 减少js对DOM的访问Minimize DOM Access
三种方法:
1. 缓存DOM元素的引用
2. 批处理对DOM的修改,而不是每次都调用DOM方法(stackoverflow上有一个相关的回答:http://stackoverflow.com/questions/14291811/minimize-dom-access-inorder-to-have-a-more-responsive-page)
3. 避免使用js来解决布局上的问题
26. 更明智地使用事件监听器
假如你在一个div下有十个按钮,可以只在那个div上添加事件监听(再通过Event参数分清来源),因为事件会冒泡的。
又比如监听DOMContentLoaded事件而不是Load事件。
27. 使用 <link>而不是 @import
这篇文章:
高性能网站设计:不要使用@import
已经交代了一切。
又可以黑IE了。
28. 避免用AlphaImageLoader
Yet another历史问题。现在无须担心这个了。
29. 优化图片
其实除了在js和css文件上动刀子,我们也可以优化图片。
文中提到了用PNG代替GIF(所以说里面的内容已经有点老了)
还推荐一些工具,如imagepicker、pngcrush、jpegtran。
总之,去除多余的图像信息,如果允许,可以牺牲下图片质量。
30. 优化CSS Sprites
将小图片水平排放而不是竖直排放。
减少图片间的间隔。
使用上述的优化图片的方式。
31. 不要在HTML中设置图片
不要使用过大的图片,然后在HTML里给它设置一个合适的(更小的)尺寸。
32. 让favicon.ico小而易于缓存
favicon.ico应该小于1k。可以考虑给它设置一个Expire报头,如果你有打算修改它的话,毕竟你不能改变它的命名。
33. 让组件小于25K
之所以要小于25K这个Magic Number,是因为iPhone不会缓存大于25K的组件。意味着如果有的文件大于25K,每次访问时都需要重新获取。
不过考虑到本文内容较老,我还是搜索求证下,最后找到了这个网址:
http://www.slideshare.net/cafenoirdesign/the-future-is-mobile-11719438 (需梯子)
Double image dimensions, then resize✤ Individual component caching: iOS 3.x will only cache HTML pages under 25k , iOS 4 102.4 kb per item✤ Total component caching: Android and iOS 4 set limit at 2MB✤ gzip has no effect on cache-ability on any device
简单说,25K限制是iOS 3时代的产物,对于现在的移动端,基本上不需要担心组件太大而无法缓存的问题(不过用户会比你更在意流量耗费的问题)。
34. 将组件打包成multipart类型
指定Content-Type为multipart,然后在一个响应报文中发布多段数据……呃,基本上没什么会用吧,太过于复杂而且效果不显著。
35. <img>
避免空的src属性
这种情况有两种版本:
<!-- html --><img src="">
// javascriptvar img = new Image();
img.src = "";
都会导致额外的、徒劳无功的请求。
同理,以下代码也有同样的问题:
<script src=""><link href="">
不过在HTML5中规定,只有合法的URL引用才会产生新的请求,所以如今类似这样的空属性不应该会带来额外的负担。