1

原文见此:https://developer.yahoo.com/performance/rules.html

注意,不是翻译,只是谈谈本人的读后感。

另外注意,该文比较旧,大概是2010年的产物,所以里面会有些跟不上时代的内容。

1. 减少HTTP请求

一个典型的http请求报文大概是这样的:

GET /3.3.0/build/yui/yui-min.js 
HTTP/1.1Host: yui-s.yahooapis.com
Pragma: no-cache
Cache-Control: no-cache...

虽然也就几行文字,但是考虑到http协议里,对同一个域名同时发出的请求是受限的[1],如果请求太多,说不定它们会堵塞在队列中呢。哦,对了,忘记把cookie算上去了,每次请求的时候都会附带当前域下的cookie,有时候关这一项就有几百B呢。

解决方法:

  1. 文件打包使用CSS Sprites: 将小图标们合并成一幅大背景图,再通过恰当地设置background-imagebackground-position来取出。根据你所用的语言和框架,一般都能找到相关的工具来完成这一任务。除了可以打包图片,还可以打包css文件和js文件。把多个相关js文件和css文件打包成单一的js文件或css文件,省下的http请求数量。这也可以交由工具去做。比如Flask可以使用Flask-Assets

  2. 内联图片可以用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="">
// javascript
var img = new Image();
img.src = "";

都会导致额外的、徒劳无功的请求。

同理,以下代码也有同样的问题:

<script src="">
<link href="">

不过在HTML5中规定,只有合法的URL引用才会产生新的请求,所以如今类似这样的空属性不应该会带来额外的负担。


spacewander
5.6k 声望1.5k 粉丝

make building blocks that people can understand and use easily, and people will work together to solve the very largest problems.