从雅虎军规看前端性能优化
本文大部分内容翻译自雅虎前端的性能优化,如何让页面加载更快,雅虎给出了多个规则,原文地址:Best Practices for Speeding Up Your Web Site 。主要从八个方向分别介绍了如何进行性能的优化。
1. Content
1.1 最小化HTTP请求
雅虎军规上说明80%的响应时间都来自前端,大多数页面的加载时间都是在下载图片,样式,js,flash等,减少组件的数量反过来减少请求的数量是页面加载更快的关键。
减少页面组件数量的一种方法是简化页面设计,但是如何在构建更丰富内容的基础上,同时还能减少相应时间?
-
Combined file ,合并文件,可以通过合并
JavaScript
,CSS
文件来减少HTTP请求的数量来缩短响应时间。 -
CSS Sprites ,CSS精灵,是减少图片请求数量的首选方法,通过将背景图合并为单个图像, 通过
background-image
和background-position
属性来显示部分需要的图像。 - Image maps ,图像地图,通过将多张图片合成为一张图片,整体大小大致相同,但减少HTTP请求的数量会加快页面的速度。 一般用于如导航条 ,定义图像坐标容易出错,不推荐使用。
-
Inline images ,内联图像,使用
data:url scheme
将图像嵌入实际页面中。
1.2 减少DNS查找
DNS就像电话簿将人们的姓名映射到他们的电话号码一样,当您输入www.yahoo.com
时,浏览器会通过DNS解析返回服务器的IP地址,这个DNS解析过程需要成本,通常需要20-120ms才能解析成功,在这之前,浏览器无法从服务器获取任何内容。
通过缓存DNS查找来获得更好的性能。DNS信息保留在操作系统的DNS缓存中,大多数的浏览器都有自己的缓存,与操作系统的分开。
默认情况,IE会将DNS查找缓存30分钟,FireFox缓存一分钟。
当客户端的DNS缓存为空(对于浏览器和操作系统)时,DNS查找的数量等于网页中唯一主机名的数量。 减少唯一主机名的数量可减少DNS查找的数量。
减少唯一主机名的数量有可能减少页面中发生的并行下载量。避免DNS查找会缩短响应时间,但减少并行下载可能会缩短响应时间。 准则是将这些组件分成至少两个但不超过四个主机名。这是减少DNS查找和允许高度并行下载之间的良好折衷。
1.3 避免重定向
使用301和302状态码完成重定向。下面是一个301响应http头示例:
HTTP/1.1 301 Moved Permanently
Location: http://example.com/newuri
Content-Type: text/html
浏览器自动将用户带到Location
字段指定的URL。跳转所需的所有信息都在http头 ,响应的主体通常是空的。301或302响应一般不会被缓存,除非有Expires
或者Cache-Control
指定要缓存。
要记住的主要事情是重定向会降低用户体验。在用户和HTML文档之间插入重定向会延迟页面中的所有内容,因为页面中的任何内容都无法呈现,并且在HTML文档到达之前不会开始下载任何组件。
最浪费的重定向之一经常发生,就是在URL中缺少尾部/
会产生301响应,比如http://astrology.yahoo.com/astrology
会301
跳转到http://astrology.yahoo.com/astrology/
。
1.4 使Ajax可缓存
使Ajax可缓存的好处之一就是在用户请求时可以提供快速反馈,因为它从后端Web服务器异步请求信息。重要的是要记住“异步”并不意味着“瞬时”。
为了提高性能,优化这些Ajax响应非常重要。提高Ajax性能的最重要方法是使响应可缓存,其中提高的方法除了Add an Expires or a Cache-Control Header 中讨论的之外,其他方法还有:
- gzip组件
- 减少DNS查找
- 压缩JS
- 避免重定向
- 设置ETags
1.5 延迟加载组件
你可以自习看看你的页面并问问你自己,最初页面的渲染需要什么,其他的内容和组件就是可以延迟加载的。
JavaScript
是在 onload
时间之前和之后拆分的理想候选者,例如,如果您有拖放和动画的JS代码,则可以延迟加载,因为它需要在页面渲染完之后才可以执行。其它可延迟的包括隐藏的内容,折叠起来的图片等等。
1.6 预加载组件
预加载看起来和延迟加载相反,但它实际上有着不同的目标,通过预加载组件,您可以利用浏览器空闲的时间并请求将来需要的组件(如图像,样式和脚本)。这样,当用户访问下一页时,您可以将大部分组件放在缓存中,并且用户加载页面将更快。
有几种预加载类型:
- 无条件预加载:一旦
onload
触发,你立即获取另外的组件。比如谷歌会在主页这样加载搜索结果页面用到的雪碧图。 - 有条件预加载:基于用户操作,您可以进行有根据的猜测,即用户前进的位置并相应地预加载。
- 预期的预加载:在旧网页预加载新网页的部分组件,那么切换到新网页时就不会是没有任何缓存了。
1.7 减少DOM数量
复杂页面意味着要下载更多字节,这也意味着JavaScript中的DOM访问速度更慢。例如,当您想要添加事件处理程序时,如果在页面上循环遍历500或5000个DOM元素,则会有所不同。
1.8 跨域拆分组件
拆分组件来达到最大化的并行下载,由于DNS查询的副作用,最好保证使用的域名不准超过2-4个。例如,您可以托管HTML和动态内容,www.example.org
并在static1.example.org
和之间拆分静态组件。
1.9 最少的iframe
iframe允许html文档被插入到父文档。
<iframe>
优点:
- 帮助解决缓慢的第三方内容的加载,如广告和徽章
- 安全沙盒
- 并行下载脚本
<iframe>
缺点:
- 即使空的也消耗(资源和时间)
- 阻塞了页面的
onload
- 非语义化(标签)
1.10 不要出现404
HTTP的请求是非常昂贵的,因此发出的HTTP请求获得无用的响应是完全没有必要的,并且会影响用户体验。
一些网站会有特别的404页面提高用户体验,但这仍然会浪费服务器资源。特别坏的是当链接指向外部js但却得到404结果。这样首先会降低(占用)并行下载数,其次浏览器可能会把404响应体当作js来解析,试图从里面找出可用的东西。
2. Server
2.1 使用CDN
用户与Web服务器的距离会对响应时间产生影响。在多个地理位置分散的服务器上部署内容将使您的页面从用户的角度加载更快。
CDN是一群不同地点的服务器,可以更高效地分发内容到用户。
2.2 添加Expries
或者 Cache-Control
头
这条规则有两个方面:
- 对于静态组件:通过设置
Expires
头实现“永不过期”策略 - 对于动态组件:使用适当的
Cache-Control
标头来帮助浏览器处理条件请求
页面内容越来越丰富,意味着页面中有更多脚本,样式表,图像以及Flash。您的页面的首次访问可能必须发出多个HTTP请求,但通过使用Expires标头,您可以使这些组件可缓存。
浏览器(和代理)使用缓存来减少HTTP请求的数量和大小,从而加快网页加载速度。Web服务器使用HTTP响应中的Expires头来告诉客户端可以缓存组件多长时间。 比如:
Expires: Thu, 15 Apr 2010 20:00:00 GMT
表示在2010-04-15都可以请求缓存内容。
2.3 Gzip组件
通过前端工程师做出的决策,可以显著减少在网络上传输HTTP请求和响应所需的时间。从HTTP / 1.1开始,Web客户端表示支持使用HTTP请求中使用Accept-Encoding进行压缩。
Accept-Encoding:gzip,deflate
如果服务器看到这个头部,它可能会选用列表中的某个方法压缩响应。服务器通过Content-Encoding
头部提示客户端:
Content-Encoding: gzip
gzip一般可减小响应的70%。尽可能去gzip更多(文本)类型的文件。html,脚本,样式,xml和json等等都应该被gzip,而图片,pdf等等不应该被gzip,因为它们本身已被压缩过,gzip压缩它们只是浪费cpu,甚至增加文件大小。
尽可能多地压缩文件类型是减轻页面重量和加速用户体验的简便方法。
2.4 配置ETag
实体标记(ETag)是Web服务器和浏览器用于确定浏览器缓存中的组件是否与源服务器上的组件匹配的机制。 添加ETag以提供验证比上次修改日期更灵活的实体的机制。ETag是唯一标识组件的特定版本的字符串。 服务器这样设置组件的ETag:
HTTP/1.1 200 OK
Last-Modified: Tue, 12 Dec 2006 03:03:59 GMT
ETag: "10c24bc-4ab-457e1c1f"
Content-Length: 12195
之后,如果浏览器要验证组件,它用If-None-Match
头部来传ETag给服务器。如果ETag匹配,服务器返回304:
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
2.5 尽早刷新Buffer
当用户请求页面时,后端服务器可能需要200到500毫秒才能将HTML页面拼接在一起。在此期间,浏览器在等待数据到达时处于空闲状态。 在PHP中,有函数flush()
。它允许您将部分准备好的HTML响应发送到浏览器,以便浏览器可以在后端忙于HTML页面的其余部分时开始获取组件。这种好处主要出现在繁忙的后端或轻量级前端。
一个比较好的flush的位置是在head
之后,因为浏览器可以加载其中的样式和脚本文件,而后台继续生成页面剩余部分。
<!-- css, js -->
</head>
<?php flush(); ?>
<body>
<!-- content -->
2.6 AJAX 使用 GET 请求
在雅虎邮件团队发现,在使用时XMLHttpRequest
,POST在浏览器中实现为两步过程:首先发送头部,然后发送数据。因此最好使用GET,它只需要一个TCP数据包发送(除非你有很多cookie)。IE中的最大URL长度为2K,因此如果发送的数据超过2K,则可能无法使用GET。
POST不提交任何数据跟GET行为类似,但从语义上讲,获取数据应该用GET,提交数据到服务器用POST。
2.7 避免空src的图片
空src属性的图片的行为可能跟你预期的不一样。它有两种形式:
- html标签:
<img src="">
- js:
var img = new Image(); img.src = "";
两种形式都会产生相同的效果:浏览器向您的服务器发出另一个请求
- Internet Explorer向页面所在的目录发出请求。
- Safari和Chrome会向实际页面提出请求。
- Firefox 3及更早版本的行为与Safari和Chrome相同,但3.5版解决了此问题[错误444931],不再发送请求。
- 遇到空图像时,Opera不执行任何操作。
为什么这种行为不好?
- 由于发送大量的意料之外的流量,会削弱服务器,尤其那些每天pv上百万的页面。
- 浪费服务器计算周期取生成不会被浏览的页面。
- 可能会破坏用户数据。如果你在跟踪请求状态,通过cookie或其它,你可能会破坏数据。即使image的请求不会返回图片,但所有的头部数据都被浏览器读取了,包括cookie。即使剩下的响应体被丢弃,破坏可能已经发生。
3. Cookie
3.1 减小Cookie大小
http cookie的使用有多种原因,比如授权和个性化。cookie的信息通过http头部在浏览器和服务器端交换。尽可能减小cookie的大小来降低响应时间。
- 消除不必要的cookie。
- 尽可能减小cookie的大小来降低响应时间。
- 注意设置cookie到合适的域名级别,则其它子域名不会被影响。
- 正确设置Expires日期。早一点的Expires日期或者没有会尽早删除cookie,优化响应时间。
3.2 用没有cookie的域名提供组件。
当浏览器发出静态图像请求并将cookie与请求一起发送时,服务器对这些cookie没有任何用处。所以他们只是没有充分理由创建网络流量。您应该确保使用无cookie请求请求静态组件。创建一个子域并在那里托管所有静态组件。
如果您的域名是www.example.org
,您可以托管您的静态组件static.example.org
。但是,如果您已经在顶级域上设置了cookie example.org
而不是www.example.org
,则所有请求都 static.example.org
将包含这些cookie。在这种情况下,您可以购买一个全新的域,在那里托管您的静态组件,并保持此域无cookie
4. CSS
4.1 将CSS放在顶部
在研究Yahoo!的性能时,我们发现将样式表移动到文档HEAD会使页面看起来加载速度更快。这是因为将样式表放在HEAD中允许页面逐步呈现。
关注性能的前端工程师希望页面被逐步渲染,这时因为,我们希望浏览器尽早渲染获取到的任何内容。这对大页面和网速慢的用户很重要。给用户视觉反馈,比如进度条的重要性已经被大量研究和记录。在我们的情况中,HTML
页面就是进度条。当浏览器逐步加载页面头部,导航条,logo等等,这些都是给等待页面的用户的视觉反馈。这优化了整体用户体验。
把样式表放在文档底部的问题是它阻止了许多浏览器的逐步渲染,包括IE。这些浏览器阻止渲染来避免在样式更改时需要重绘页面元素。所以用户会卡在白屏。
4.2 避免CSS表达式
CSS表达式是强大的(可能也是危险的)设置动态CSS属性的方法。IE5开始支持,IE8开始不赞成使用。例如,背景颜色可以设置成每小时轮换:
background-color: expression( (new Date()).getHours()%2 ? "#B8D4FF" : "#F08A00" );
表达式的问题在于它们的评估频率高于大多数人的预期。它们不仅在页面呈现和调整大小时进行重新计算,而且在页面滚动时甚至在用户将鼠标移动到页面上时进行计算。在CSS表达式中添加计数器可以让我们跟踪CSS表达式的计算时间和频率。在页面上移动鼠标可以轻松计算超过10,000次。
4.3 选择<link>
而不是@import
之前的一个最佳原则是说CSS应该在顶部来允许逐步渲染。
在IE用@import
和把CSS放到页面底部行为一致,所以最好别用。
4.4 避免过滤器
专有的AlphaImageLoader
过滤器旨在解决IE版本<7中的半透明真彩色PNG的问题。该过滤器的问题在于它在下载图像时阻止渲染并冻结浏览器。它还会增加内存消耗,并且每个元素应用,而不是每个图像,因此问题成倍增加。
最佳做法是放弃AlphaImageLoader
,改用PNG8来优雅降级。
5. JavaScript
5.1 将Script放在底部
脚本引起的问题是它们阻塞了并行下载。 HTTP1.1规范建议浏览器每个域名下不要一次下载超过2个组件。如果你的图片分散在不同服务器,那么你能并行下载多个图片。但当脚本在下载,浏览器不会再下载其它组件,即使在不同域名下。
有些情况下把脚本移动到底部并不简单。比如,脚本中用了document.write
来插入内容,它就不能被移动到底部。另外有可能有作用域问题。但大多数情况,有方法可以解决这些问题。
一个替代建议是使用异步脚本。defer
属性表明脚本不包含document.write
,是提示浏览器继续渲染的线索。
5.2 使用外部JavaScript 和 CSS
在实际中使用外部文件通常会产生更快的页面,因为浏览器会缓存JavaScript和CSS文件。每次请求HTML文档时,都会下载HTML文档中内联的JavaScript和CSS。这减少了所需的HTTP请求数,但增加了HTML文档的大小。另一方面,如果JavaScript和CSS位于浏览器缓存的外部文件中,则HTML文档的大小会减少,而不会增加HTTP请求的数量。
5.3 压缩JavaScript 和 CSS
压缩就是删除代码中不必要的字符来减小文件大小,从而提高加载速度。当代码压缩时,注释删除,不需要的空格(空白,换行,tab)也被删除。
5.4 删除重复的脚本
在一个页面中两次包含相同的JavaScript文件会损害性能。这并不像你想象的那么不寻常。对美国十大顶级网站的评论显示,其中两个网站包含重复的脚本。两个主要因素会增加脚本在单个网页中重复的几率:团队规模和脚本数量。当它发生时,重复的脚本会通过创建不必要的HTTP请求和浪费的JavaScript执行来损害性能。
发出不必要的http请求发生在IE而不是Firefox。在IE,如果外部脚本引入两次且没有缓存,它会发出2个请求。即使脚本被缓存,刷新时也会发出额外请求。
除了增加http请求,时间被浪费在执行脚本多次上。不管IE还是Firefox都会执行多次。
5.5 最小化DOM访问
使用JavaScript访问DOM元素的速度很慢,因此为了获得响应更快的页面,您应该:
- 缓存访问过的元素的引用
- 在DOM树外更新节点,然后添加到DOM树
- 避免用JS实现固定布局
5.6 使用事件代理
有时候页面看起来不那么响应(响应速度慢),是因为绑定到不同元素的大量事件处理函数执行太多次。这是为什么使用事件委托是一种好方法。
另外,你不必等到onload
事件来开始处理DOM树,DOMContentLoaded
更快。大多时候你需要的只是想访问的元素已在DOM树中,所以你不必等到所有图片被下载。
6. images
6.1 优化图片
- 检查GIF并查看它们是否使用与图像中颜色数对应的调色板大小。
- 可以把gif转成png看看有没有变小。除了动画,gif一般可以转成png8
- 运行
pngcrush
或其它工具压缩png。 - 运行
jpegtran
或其它工具压缩jpeg。
6.2 优化CSS精灵图
- 将图像水平排列在精灵图中而不是垂直排列通常会导致文件较小。
- 把颜色近似的图片合并到一张精灵图,这样可以让颜色数更少,如果低于256就可以用png8.
- “适应移动设备”并且不要在精灵中留下大的间隙。这不会影响文件大小,但需要较少的内存,以便用户代理将图像解压缩为像素图。
6.3 不要在HTML中缩放图片
不要使用比您需要的更大的图像,因为您可以在HTML中设置宽度和高度。如果您需要, <img width="100" height="100" src="mycat.jpg" alt="My Cat" />
那么您的图像(mycat.jpg)应该是100x100px而不是缩小的500x500px图像。
6.4 让 favicon.ico
小且可缓存
favicon.ico是在你服务器根路径的图片。邪恶的是即使你不关心它,浏览器仍然会请求它。所以最好不要响应404。另外由于在同一服务器,每次请求favicon.ico时也会带上cookie。这个图片还会影响下载顺序,比如在IE,如果你在onload
时下载额外的组件,fcvicon会在这些组件之前被下载。
怎么减轻favicon.ico的缺点?
- 小,最好1K以下
- 设置Expires头部。也许可以安全地设置为几个月。
7. Mobile
7.1 保持组件小于25K
此限制与iPhone不会缓存大于25K的组件这一事实有关。请注意,这是未压缩的大小。在这里减少组件大小很重要,因为单独使用gzip可能还不够。
7.2 将组件打包到多部分文档中
将组件打包到多部分文档就像带有附件的电子邮件,它可以帮助您通过一个HTTP请求获取多个组件(请记住:HTTP请求很昂贵)。使用此技术时,首先检查用户代理是否支持它(iPhone不支持)。