雅虎前端优化的35条军规。
内容部分
1. 尽量减少HTTP请求数
80% 的终端用户响应时间都花在了前端上,其中大部分时间都在下载页面上的各种组件:图片,样式表,脚本,Flash等等。减少组件数必然能够减少页面提交的HTTP请求数。 这是让页面更快的关键。
合并文件 是通过把所有脚本放在一个文件中的方式来减少请求数的,当然,也可以合并所有的 CSS。
CSS Sprites 是减少图片请求数量的首选方式。把背景图片都整合到一张图片中,然后用 CSS 的 background-image
和 background-position
属性来定位要显示的部分。
图像映射 可以把多张图片合并成单张图片,总大小是一样的,但减少了请求数并加速了页面加载。图片映射只有在图像在页面中连续的时候才有用,比如导航条。
行内图片(Base64编码) 用 data: URL
模式来把图片嵌入页面。
2. 减少DNS查找
在 DNS 查找完成之前,浏览器无法从主机名下载任何东西。
DNS 查找被缓存起来更高效,由用户的 ISP(网络服务提供商)或者本地网络存在一个特殊的缓存服务器上,但还可以缓存在个人用户的计算机上。只要浏览器在自己的 cache 里还保留着这条记录,它就不会向操作系统查询 DNS。
如果客户端的 DNS cache 是空的(包括浏览器的和操作系统的),DNS 查找数等于页面上不同的主机名数,包括页面URL,图片,脚本文件,样式表,Flash 对象等等组件中的主机名,减少不同的主机名就可以减少 DNS 查找。
把组件分散在 2 到 4 个主机名下,这是同时减少 DNS 查找和允许高并发下载的折中方案。
3. 避免重定向
重定向用 301 和 302 状态码,下面是一个有 301 状态码的 HTTP 头:
HTTP/1.1 301 Moved Permanently
Location: http://example.com/newuri
Content-Type: text/html
浏览器会自动跳转到 Location 域指明的 URL。重定向需要的所有信息都在 HTTP 头部,而响应体一般是空的。其实额外的 HTTP 头,比如 Expires 和 Cache-Control 也表示重定向。除此之外还有别的跳转方式:refresh 元标签和 JavaScript,但 如果你必须得做重定向,最好用标准的 3xxHTTP 状态码,主要是为了让返回按钮能正常使用。
有一种常见的 极其浪费资源 的重定向,而且 web 开发人员一般都意识不到这一点,就是URL 尾部缺少一个斜线的时候。
一种替代方案是用 Alias 和 mod_rewrite
,前提是两个代码路径都在相同的服务器上。如果是因为域名变化而使用了重定向,就可以创建一条 CNAME(创建一个指向另一个域名的 DNS 记录作为别名)结合 Alias 或者 mod_rewrite 指令。
4. 让Ajax可缓存
Ajax 的一个好处是可以给用户提供即时反馈,因为它能够从后台服务器异步请求信息。
最重要的提高 Ajax 性能的方法就是 让响应变得可缓存,下面适用于 Ajax 的其它规则:
- Gzip 组件
- 减少 DNS 查找
- 压缩 JavaScript
- 避免重定向
- 配置 ETags
5. 延迟加载组件
可以凑近看看页面并问自己:什么才是一开始渲染页面所必须的?其余内容都可以等会儿。
工具可帮你减轻工作量:YUI Image Loader
可以延迟加载折叠的图片,还有 YUI Get utility
是一种引入 JS 和 CSS 的简单方法。
最好让性能目标符合其它 web 开发最佳实践,比如 “渐进增强” 。如果客户端支持 JavaScript,可以提高用户体验,但必须确保页面在不支持 JavaScript 时也能正常工作。所以,在确定页面运行正常之后,可以用一些延迟加载脚本增强它,以支持一些拖放和动画之类的华丽效果。
6. 预加载组件
预加载的类型:
-
无条件预加载 —— 尽快开始加载,获取一些额外的组件。
google.com
就是一个 sprite 图片预加载的好例子,这个 sprite 图片并不是 google.com 主页需要的,而是搜索结果页面上的内容。 -
条件性预加载 —— 根据用户操作猜测用户将要跳转到哪里并据此预加载。
在
search.yahoo.com
的输入框里键入内容后,可以看到那些额外组件是怎样请求加载的。 -
提前预加载 —— 在推出新设计之前预加载。
可以通过在将要推出新设计之前预加载一些组件来减轻这种负面影响,老站可以利用浏览器空闲的时间来请求那些新站需要的图片和脚本。
7. 减少DOM元素的数量
一个复杂的页面意味着要下载更多的字节,而且用 JavaScript 访问 DOM 也会更慢。
或许应该 用更好的语义化标记。
DOM元素的数量很容易测试,只需要在 Firebug 的控制台里输入:
document.getElementsByTagName('*').length
那么多少 DOM 元素才算是太多呢?可以参考其它类似的标记良好的页面,例如 Yahoo! 主页是一个相当繁忙的页面,但只有不到 700
个元素(HTML标签)。
8. 跨域分离组件
分离组件可以最大化并行下载,但要确保只用不超过 2 - 4
个域,因为存在 DNS 查找的代价。
例如,可以把 HTML 和动态内容部署在 www.example.org,而把静态组件分离到 static1.example.org 和 static2.example.org。
9. 尽量少用iframe
<iframe>
的优点:
- 引入缓慢的第三方内容,比如标志和广告
- 安全沙箱
- 并行下载脚本
<iframe>
的缺点:
- 代价高昂,即使是空白的 iframe
- 阻塞页面加载
- 非语义
10. 杜绝404
HTTP 请求代价高昂,完全没有必要用一个 HTTP 请求去获取一个无用的响应(比如404 Not Found),只会拖慢用户体验而没有任何好处。
有些站点用的是有帮助的 404——“你的意思是xxx?”,这样做有利于用户体验,但也浪费了服务器资源(比如数据库等等)。
css部分
11. 避免使用CSS表达式
用 CSS 表达式动态设置 CSS 属性,是一种强大又危险的方式。从 IE5 开始支持,但从 IE8 起就不推荐使用了。
12. 选择<link>舍弃@import
前面提到了一个最佳实践:为了实现逐步渲染,CSS 应该放在顶部。
在 IE 中用 @import
与在底部用 <link>
效果一样,所以最好不要用它。
13. 避免使用滤镜
IE 专有的 AlphaImageLoader
滤镜可以用来修复 IE7 之前的版本中半透明 PNG 图片的问题。在图片加载过程中,这个滤镜会阻塞渲染,卡住浏览器,还会增加内存消耗而且是被应用到每个元素的,而不是每个图片,所以会存在一大堆问题。
14. 把样式表放在顶部
把样式表放到文档的 HEAD
部分能让页面看起来加载地更快。
js部分
15. 去除重复脚本
重复脚本会创建不必要的 HTTP 请求,执行无用的 JavaScript 代码,而影响页面性能。
避免不小心把相同脚本引入两次的一种方法就是在模版系统中 实现脚本管理模块。典型的 脚本引入方法 就是在 HTML 页面中用 SCRIPT 标签:
<script type="text/javascript" src="menu_1.0.17.js"></script>
16. 尽量减少DOM访问
用 JavaScript 访问 DOM 元素是很慢的,所以,为了让页面反应更迅速,应该:
- 缓存已访问过的元素的索引
- 先“离线”更新节点,再把它们添到 DOM 树上
- 避免用 JavaScript 修复布局问题
17. 用智能的事件处理器
有时候感觉页面反映不够灵敏,是因为有太多频繁执行的事件处理器被添加到了 DOM 树的不同元素上,这就是推荐 使用事件委托 的原因。
如果一个 div 里面有 10 个按钮,应该只给 div 容器添加一个事件处理器,而不是给每个按钮都添加一个。
事件能够冒泡,所以可以捕获事件并得知哪个按钮是事件源。
18. 把脚本放在底部
脚本会阻塞并行下载,HTTP/1.1 官方文档建议 浏览器每个主机名下并行下载的组件数不要超过两个,如果图片来自多个主机名,并行下载的数量就可以超过两个。 如果脚本正在下载,浏览器就不开始任何其它下载任务,即使是在不同主机名下的。
有时候,并不容易把脚本移动到底部。举个例子,如果脚本是用 document.write 插入到页面内容中的,就没办法再往下移了。还可能存在作用域问题,在多数情况下,这些问题都是可以解决的。
一个常见的建议是用 推迟(deferred)脚本 ,有 DEFER
属性的脚本意味着不能含有 document.write,并且提示浏览器告诉他们可以继续渲染。
javascript, css
19. 把JavaScript和CSS放到外面
应该把 JavaScript 和 CSS 放到外部文件中还是直接写在页面里?
实际上,用外部文件可以让页面更快 ,因为 JavaScript 和 CSS 文件会被缓存在浏览器。
HTML 文档中的行内 JavaScript 和 CSS 在每次请求该 HTML 文档的时候都会重新下载。这样做减少了所需的 HTTP 请求数,但增加了 HTML 文档的大小。另一方面,如果 JavaScript 和 CSS 在外部文件中,并且已经被浏览器缓存起来了,那么我们就成功地把 HTML 文档变小了,而且还没有增加 HTTP 请求数。
20. 压缩JavaScript和CSS
压缩 具体来说就是从代码中去除不必要的字符以减少大小,从而提升加载速度。代码最小化 就是去掉所有注释和不必要的空白字符(空格,换行和tab)。在 JavaScript 中这样做能够提高响应性能,因为要下载的文件变小了。两个最常用的 JavaScript 代码压缩工具是 JSMin
和 YUI Compressor
,YUI compressor还可以压缩CSS。
混淆 是一种可选的源码优化措施,要比压缩更复杂。
图片
21. 优化图片
尝试把 GIF 格式转换成 PNG
格式,看看是否节省空间。
在所有的 PNG 图片上运行 pngcrush
(或者其它 PNG 优化工具)。
22. 优化CSS Sprite
在 Sprite 图片中 横向排列 一般都比纵向排列的最终文件小 。
组合 Sprite 图片中的 相似颜色可以保持低色数 ,最理想的是 256 色以下 PNG8 格式。
“对移动端友好”,不要在 Sprite 图片中留下太大的空隙。 虽然不会在很大程度上影响图片文件的大小,但这样做可以节省用户代理把图片解压成像素映射时消耗的内存。
23. 不要用HTML缩放图片
不要因为在 HTML 中可以设置宽高而使用本不需要的大图。如果需要
<img width="100" height="100" src="mycat.jpg" alt="My Cat" />
那么图片本身(mycat.jpg)应该是 100x100px 的,而不是去缩小 500x500px 的图片。
24. 用小的可缓存的favicon.ico
为了缓解 favicon.ico 的缺点,应该确保:
- 足够小,最好在 1K 以下
- 设置合适的有效期 HTTP 头(以后如果想换的话就不能重命名了),把有效期设置为几个月后一般比较安全,可以通过检查当前 favicon.ico 的最后修改日期来确保变更能让浏览器知道。
cookie
25. 给Cookie减肥
- 清除不必要的 cookie。
- 保证 cookie 尽可能小,以最小化对用户响应时间的影响。
- 注意给 cookie 设置合适的域级别,以免影响其它子域。
- 设置合适的有效期,更早的有效期或者 none 可以更快的删除 cookie,提高用户响应时间。
26. 把组件放在不含cookie的域下
当浏览器发送对静态图像的请求时,cookie 也会一起发送,而服务器根本不需要这些 cookie。所以它们只会造成没有意义的网络通信量,应该确保对静态组件的请求不含cookie。可以创建一个子域,把所有的静态组件都部署在那儿。
移动端
27. 保证所有组件都小于25K
这个限制是因为 iPhone 不能缓存大于 25K 的组件,注意这里指的是 未压缩的大小。
28. 把组件打包到一个复合文档里
把各个组件打包成一个像有附件的电子邮件一样的复合文档里,可以用一个 HTTP 请求获取多个组件(记住一点:HTTP 请求是代价高昂的)。用这种方式的时候,要先检查用户代理是否支持(iPhone就不支持)。
服务器
29. Gzip组件
压缩可以通过减少 HTTP 响应的大小来缩短响应时间。
从 HTTP/1.1 开始,web 客户端就有了支持压缩的 Accept-Encoding HTTP
请求头。
Accept-Encoding: gzip, deflate
如果 web 服务器看到这个请求头,它就会用客户端列出的一种方式来压缩响应。web 服务器通过 Content-Encoding
相应头来通知客户端。
Content-Encoding: gzip
尽可能多地 用 gzip 压缩 能够给页面减肥,这也是提升用户体验最简单的方法。
30. 避免图片src属性为空
src 属性为空 会引起 浏览器会向服务器发送另一个请求 的问题。
31. 配置ETags
实体标签(ETags)
,是服务器和浏览器用来决定浏览器缓存中组件与源服务器中的组件是否匹配的一种机制(“实体”也就是组件:图片,脚本,样式表等等)。
添加 ETags 可以提供一种实体验证机制,比最后修改日期更加灵活。一个 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 传回源服务器。如果 ETags 匹配成功,会返回一个 304 状态码,这样就减少了 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
32. 对Ajax用GET请求
使用 XMLHttpRequest
时,浏览器的 POST 请求 是通过一个两步的 过程 来实现的:先发送 HTTP 头,再发送数据。所以 最好用 GET 请求,它 只需要发送一个 TCP 报文(除非 cookie 特别多)。
IE 的 URL 长度最大值是 2K,所以如果要发送的数据超过 2K 就无法使用GET了。
POST 请求的一个有趣的副作用是实际上没有发送任何数据,就像 GET 请求一样。正如 HTTP 说明文档中描述的,GET 请求是用来检索信息的。 所以它的语义只是用 GET 请求来请求数据,而不是用来发送需要存储到服务器的数据。
33. 尽早清空缓冲区
当用户请求一个页面时,服务器需要用大约 200 到 500 毫秒
来组装 HTML 页面,在这期间,浏览器闲等着数据到达。
PHP 中有一个 flush()
函数,允许给浏览器发送一部分已经准备完毕的 HTML 响应,以便浏览器可以在后台准备剩余部分的同时开始获取组件,好处 主要体现在很忙的后台或者很“轻”的前端页面上。
较理想的清空缓冲区的位置是 HEAD 后面,因为 HTML 的 HEAD 部分通常更容易生成,并且允许引入任何 CSS 和 JavaScript 文件,这样就可以让浏览器在后台还在处理的时候就开始并行获取组件。
34. 使用CDN(内容分发网络)
用户与服务器的 物理距离 对响应时间也有影响。把内容部署在多个地理位置分散的服务器上能让用户更快地载入页面。
最好先分散静态内容,而不是一开始就重新设计应用程序结构。
内容分发网络(CDN) 是一组分散在不同地理位置的 web 服务器,用来给用户更高效地发送内容。典型地,选择用来发送内容的服务器是基于网络距离的衡量标准的。例如:选 跳数(hop) 最少的或者响应时间最快的服务器。
35. 添上Expires或者Cache-Control HTTP头
- 对于静态组件:通过设置一个遥远的将来时间作为 Expires 来实现永不失效。
- 对于动态组件:用合适的 Cache-ControlHTTP 头来让浏览器进行条件性的请求。
通过使用有效期能让组件变得可缓存,这避免了在接下来的浏览过程中不必要的 HTTP 请求。有效期 HTTP 头通常被用在图片上,但它们应该用在所有组件上,包括脚本、样式和 Flash 组件。
浏览器(和代理)用缓存来减少 HTTP 请求的数目和大小,让页面能够更快加载。web 服务器通过 有效期 HTTP 响应头 来告诉客户端,页面的各个组件应该被缓存多久。
用一个遥远的将来时间做有效期,告诉浏览器这个响应在 2020 年4月15日前不会改变。
Expires: Thu, 15 Apr 2020 20:00:00 GMT
如果你用的是 Apache 服务器,用 ExpiresDefault
指令来设置相对于当前日期的有效期。
设置从请求时间起10年的有效期:
ExpiresDefault "access plus 10 years"```