前端优化笔记
注:内容主要来自于日常阅读中读到的一些前端优化相关的文章,或者有所遗漏。
载入一个页面,分为三个阶段:浏览器打开链接并发送请求,服务器作出响应、客户端开始接收数据直至服务器端的应答完成,浏览器完成页面的渲染。
浏览器打开链接并发送请求
减少 DNS 查询次数
- 优化使用域名及子域名的数量。在 Let's make the web faster 的系列文章中1,Google 推荐使用一个主域名,辅以四个域名用于平行下载可缓存的资源。但不够六个资源时,最好不要使用超过一个域名;每个域名分配的资源不足两个显得十分浪费。最好不使用超过五个域名。
- 尽可能使用 URL 路径而非域名:例子是,
http://example.com/i-am-a-page.html及/i-am-a-page.html。 - 对于经常重复引用的文件,尽量处于同一个域名之下,例如脚本、样式文件,这样可以充分利用 DNS 的查询缓存。
- 对于 DNS 解析结果,IE 缓存 30 分钟,可以通过注册表中的 DnsCacheTimeout 来设置;Firefox 缓存 1 分钟,修改 about:config 中的 network.dnsCacheExpiration 可以更改默认值。
避免重定向的请求
发送重定向的请求有数种方式,服务器端返回 30x 并指定重定向地址,通过 meta 标签写 refresh 进行重定向,JavaScript 实现的重定向。无论哪种重定向,对 mobile 版本的网站更为致命。
对于 301/302 重定向,似乎更多是出于 SEO 的考虑,对此,应该考虑使用 Cononical Meta Tag。
使用 GET 来完成 AJAX 请求
在 AJAX 中使用 GET 请求的原因是,GET 请求很多时候只占用一个 TCP 包,而对 URL 长度最为刻薄的 IE 也只是限制 URL 在 2000 字符以内;而 POST 请求则是,先发送响应头,再发送响应中的数据。
减少 cookie 的大小
每一个请求都会包含该域名下的 cookie,怎么说也是一个不小的负担。谨慎地使用 cookie,减少 cookie 的数量和大小并不是一个坏主意。
为页面组成元素不需要发送 cookie 的域名
根据浏览器的同源原则,向同一个域名的请求总会包含该域名下的 cookie,对于静态文件来说,cookie 是不必要的。除了最大化平衡下载页面中的元素以外,无 cookie 的请求也是为静态文件启用不同域名的理由。
另外参见 使用CDN。
服务器作出应答,客户端开始接收数据
开启 HTTP 服务器中的 keepalives 选项
为免受限于浏览器全局的服务器连接数,keepalives 的超时时间可以考虑设得更短,如 5 - 10 秒。而静态文件和动态文件最好分开存在于不同的服务器;当近千个请求发送向静态文件服务器时或许仅是消耗了 10 M 内存,而你的动态文件服务器很轻易地就为每一个请求消耗 10M 内存了。
缓存 AJAX 请求的结果
AJAX 请求也是一个 HTTP 请求,即是说,是可以缓存的,expires/last-modified/cache-control/gzip 都可以用上。另一方面,对 IE 来说,对于指定了 cache-control/expires 的 AJAX 请求,在过期以前是不会重新向服务器发出请求,即使你使用 Ctrl+R 强制刷新页面。参见:Ajax Caching: Two Important Facts。
将样式文件的引用放到页面的顶部
将样式文件的引用放在页面的头部,这样页面渲染将会是渐进式的;此外,CSS 文件是并行下载的;浏览器即使还没有完整载入页面,也会发起新的请求下载 CSS 文件。
使用 <link> 而非 @import 引用样式文件
对于 IE 来说,@import 的行为就等于把样式文件放在页面的后面;而这显然不是我们所期望的。
将脚本文件的引用放到页面的底部
和样式文件不同,默认情况下,脚本文件的下载并非并行模式,无论是否属于相同的域名源,载入脚本文件时总会阻止其他文件的并行下载。并行下载脚本的实现,见 Downloading JavaScript Files in Parallel。
对于已经支持 HTML5 的浏览器,在 script 加上 defer 标签亦有同样的效果。
最后,避免重复载入同一个脚本。
尽早让页面离开缓存
这里的缓存指的是 Buffer。在 PHP 中就是在使用 ob_start() 之余,灵活地放置一些 flush()。例如,在页面头确定以后来一次 flush()。这也是实现渐进式渲染中的一个关键。
优化 CSS sprites
在 sprites 中水平地排放图片比垂直地排放图片更能减少生成图片的体积;而将相近颜色的图片合并为 sprites 可以让图片使用的颜色数目更少,甚至一张 256 色的 PNG8 也能满足需要;为了对移动设备更友好,不要在图片之间留太多的空隙,这对图片大小没有太多的影响,但对于客户端来说,这会是使用更少的内存。
避免通过 HTML 或者 CSS 代码修改图片大小
例如,在 <img> 标签中以 width 和 height 重定义图片大小并不是好主意,一张 500x500 的图片并不因此而缩减了大小。
缩减页面以及各个文件的体积
- 使用 Gzip 压缩页面。这是最早被提出的优化手段之一。往响应头里面加上 Accept-Encoding: gzip, deflate,然后使用 deflate 的方式压缩(apache 2.x 的 mod_deflate 和 Nginx 中的 Http Gzip Module)。
另外,脚本以及样式文件也推荐使用 Gzip 压缩。 - 对于 1KB 以下体积的文件,GZip 压缩的效果并不好。
- 压缩脚本和样式文件。对于精简脚本文件,推荐 Google 的 Closure-Compiler;而样式文件可以使用 YUI Compressor 或者 cssmin.js 进行处理。
- 对图片进行优化:选择更合适的图片格式,减少图片的体积。
- 对于图片来说,开启 Gzip 的作用不大。
- 设置一个体积很小的、可缓存的 favicon.ico。无论存在与否,浏览器都会向服务器请求 favicon.ico 这个文件。为了避免返回 404,最好还是让这个小图标存在,并确保:足够小的体积,甚至在 1kb 以下;设置一个遥遥无期的过期时间。
- 对于页面来说,100KB 以下的页面体积也属于一个 SEO 优化手段。
- 这主要是针对 iPhone 而非其他移动设备的优化;iPhone 仅能缓存不大于 25 KB 的单个文件,并且这是解压缩后的大小;对于一个页面的所有文件,iPhone 也仅是提供 475 - 500 KB 的缓存;更甚,页面之间是不共享缓存的,即使它们使用了同一个文件。或者对于 iPhone 来说,嵌入的样式和脚本值得考虑。
使用 HTML52
- 更短的 DTD: <!DOCTYPE html>
- 编码:<meta charset="utf-8">
- 对于 style 和 script 标签来说,type 属性可以忽略。
- 针对脚本载入而制定的新属性 async 和 defer。
优化服务器端的程序架构
好的架构有更良好的页面生成速度。对于 PHP,推荐阅读 The No-Framework PHP MVC Framework。
浏览器完成页面的渲染
针对这一部分的优化手段较多,来自 Yahoo! 的 14 条优化准测3几乎都是针对这一部分的优化。而在页面渲染的过程中,会继续向服务器的发送请求(脚本、样式文件、图片等等),对于发送请求的优化见第一部分。
数量更少的请求
对于浏览器来说,读取两个小文件比读取相同大小的一个大文件更耗费时间。
- 将多个脚本、样式文件合并为一个。实际上读取两个小文件比读取等同大小的一个大文件更耗费时间。
- CSS Sprites。优化页面中的背景图片,将多个背景图片合成一个图片。参见 A List Apart 的文章:CSS Sprites: Image Slicing’s Kiss of Death,CSS Sprites2 - It’s JavaScript Time。
- Data:URL
使用多个域名/子域名提高下载速度
子域名的启用可以让页面元素尽可能地平行化下载。例如,启用 static.yourdomain.com 为站点静态内容的域名,甚至更短的域名:ydcdn.cn。而利用多个域名/子域名优化的时候,需要考虑 DNS 的因素。
减少 iframe 的数量
iframe 等同于向服务器发送一个新的页面请求。事实上为什么要使用 iframe?
消灭 404 页面
这一点是基于用户体验出发的;当然,设计出更有用的 404 页面也是一个挑战。;)
使用 CDN
对于 CDN,如果展开了,似乎能说的有很多。首先看这篇:Amazon CloudFront vs. Rackspace Cloud Files CDN Performance。而使用 CDN 服务也得讲究性能:CDN 服务器的 uptime、DNS 解析速度、单包延迟 (single-packet latency)、丢包率等等(Understanding CDN Performance)。
对于国内的 CDN 服务,甚至说,要对 CDN 有一个更全面的认识,请看这篇:看上去很美——国内CDN现状与美国对比。
设置 expires 以及 cache-control 响应头
这是最早被提出的优化手段之一。推荐给静态文件设置一个牛年马月的过期日期,然后通过版本化的方式来更新文件。对于动态文件,则是通过 Cache-Control 这个响应头来控制缓存 4。
这里对 Cache-Control 常见的值进行一些解释。
- no-cache - 每一次强迫请求项目的缓存时,向源服务器验证缓存的可用性。
- no-store - 标记任何时间都不建立缓存。
- max-age=[seconds] - 指定缓存的存活时间。与 expires 响应头相似,但这个存活时间是相对于请求发出时的相对时间,并非绝对时间。[seconds] 为请求发出后、你期望缓存存活的秒数。
- max-stale[=senconds] - 标记响应过期后还允许存活的秒数,若不指定一个确切的时间,则是表明缓存任何时候都是有效的。
- s-maxage=[seconds] - 与 max-age 响应头的作用相近,但仅作用于分享缓存(如,代理缓存)。
- public - 标记验证过后的响应为可缓存的;一般情况下,如果 HTTP 验证是必需的,那么响应自动标记为不可缓存的(对于分享缓存来说)。
- private - 标记响应不能被可分享的缓存系统缓存;而私有缓存或许会缓存这个响应。
- must-revalidate - 指定缓存必须严格遵守指定的存活时间信息。在特殊条件下,过期的响应是可以接受的;但这个响应标明,缓存必须遵守你指定的缓存规则。
- proxy-revalidate - 于 proxy-revalidate 相似,但仅作用于代理缓存。
设置 Etags 或 Last-Modified 响应头
只能说,使用 Last-Modified 比使用 ETags 有更多的优势。这里得和 expires 和 cache-control 这两个响应头配合使用。
推迟下载页面中相对不那么重要的元素
将页面中的元素分解、剖析,那些相对不那么重要的元素可以暂缓下载和渲染,例如实现拖动和动画的脚本,无关大雅的图片或广告等等。这也是实现渐进式渲染的一个要素。
预读取部分页面元素
同样是实现渐进式渲染的一个步骤,但这一建议是将下一个页面要用到的东西提前缓存。除了有效地利用响应头(设置 expires 以及 cache-control 响应头,设置 Etags 或 Last-Modified 响应头)以外,HTML5 的 client-side storage 亦在考虑之列。
减少 DOM 节点数量
另一种表述方式是,精简页面的 HTML 架构。一个反面学习教材是 Drupal,它的 div 嵌套太多了。
减少对 DOM 进行的操作
访问 DOM 节点很慢;缓存引用 DOM 的变量,让 DOM 脱离文档树进行更新,以及避免使用脚本为 DOM 修改布局,都属于作为减少访问 DOM 节点而衍生的手段。
在进行例如动画这般的操作之前,尽可能把元素从页面流中拖出,然后使用绝对定位来定位。
减少不必要的 DOM 嵌套深度
由于对 DOM 进行的每一次操作,都可能会造成目标元素的重新渲染(reflow),因此,在对 DOM 进行操作时,尽量通过修改 class 完成样式的替换,在进行大量操作之前将其从页面正常的流中拖出,而同理加入一个元素到页面之前、先完成对该元素的操作,也尽可能把多个对页面内的 DOM 操作简化,例如合并 DOM 操作,创建一个 DocumentFragment 对象缓存多个未插入页面的DOM。
更合理地为 DOM 节点注册事件
一个常见的手段是 event delegation,假设你有 10 个按钮并且使用一个 div 节点包括,为 div 节点注册事件比为 10 个按钮各自注册事件更好。相对于浏览器的 onload 事件,在 DOMContentLoaded 事件发生后进行对 DOM 的操作更佳,后者标明页面的所有 DOM 节点已经准备就绪,而前者还需等待其他元素、例如图片的载入完成。
优化脚本运行速度
这是直接能让用户体验到的。糟糕的脚本甚至能让浏览器假死、崩溃。Google 在 Let's make the web faster 系列中有一篇针对 JavaScript 优化 的文章。
避免使用 CSS expressions
CSS expressions 是 IE 的产物,或者对于 IE 实在没有好感的前端开发者来说,是能够轻易做到的。
避免使用 filters
又一个 IE 产物。最为常用的就是 AlphaImageLoader,用于修改 IE7 以前版本的透明 png 文件。filter 的坏处是,在图片下载时,会阻碍页面的渲染进程以及让浏览器假死,也会增加每一个元素对内存的需求。对于透明 png,更好的方案是使用 gracefully degrading PNG8。
优化样式文件
- 减少样式的重复定义。例如,
h1 { color: black; } p { color: black; }改为h1, p { color: black; }。 - 适当的使用 !important 有很好的效果。
- 简化样式定义。去掉无用的样式定义。
- 合理地分割和合并样式表:Google 的建议是分割为更小的文件,仅为特定页面定制的样式独立一个小文件。
- 若非在页面载入时就使用到的样式,独立放置到一个样式表中,然后推迟这个样式表在 onload 事件以后载入。
- 假使你需要用到脚本为页面更改样式,要确保这些代码在未需使用之时不会对页面应用这些样式。
- 精简定义中的样式选择器。
- 去掉不必要的样式定义。
书写 HTML 时的奇技淫巧
- 让相近样式定义之间的定义语句书写排序尽可能地一致,如按照字母表排序(原话:“Specify CSS key-value pairs in the same order where possible, i.e. alphabetize them”,来自于 Google 的 Let's Make the Web Faster 系列文章5)。
- 让 html 代码中属性值的书写顺序尽可能地一致,例如按照字母表排序。Google 说,在他们的搜索结果页里所有链接的属性按照字母表顺序排序书写,Gzip 压缩时能减少 1.5% 的体积。
- 保持一致的字母大小写。
- 在 html 属性的书写中保持一致的引号风格,一致地使用单引号、双引号、或者甚至不使用任何引号。
不在页面内嵌入脚本以及样式
把样式和脚本分别独立成一个文件的好处不需再说。值得注意的是,嵌入的样式和脚本并非都是坏事,适当的使用有想不到的妙用。例如,平行下载脚本文件的方法就是在页面嵌入一段脚本。
渐进式渲染
更快的页面载入应该首先渲染用户可见区域的内容,然后渲染目前不可见的页面区域;其次,先载入和渲染轻量级的资源、如文字,然后载入和渲染如图片和视频那般的重量级资源。此外,使用表格布局、把样式表放置到页面底部都会致使部分浏览器的渐进式渲染失效。
为移动设备进行优化
- 创建和提供移动版本,使用习惯上指向移动版本的 URL:
m.example.com,wap.example.com,mobile.example.com。 - 为移动设备创建合适的界面。
- 减少浏览时产生请求的数量和数据传输量。
- 尽可能地启用那些新功能:程序缓存(application cache),CSS3。
- 兼容更多常见的设备:不要嵌入 flash,慎重使用 JavaScript。
其他
测试
- 经常在苛刻的网络条件下访问你的站点;这样能够更快地发现问题。Linux 2.6 内核中的 netem 和 HTB 模块,都可以结合命令行工具 tc 架设一个慢速网络代理。此外的工具包括,Firefox 的 Tamper Data,windows 下的 Fiddler,Mac OSX 中的 Charles。
- 在内网/开发环境中对页面进行压力测试。
- 建议用户为他/她的浏览器开启“流水作业”特性。例如 Firefox,在地址栏中输入 about:config,把 network.http.pipelining 设为 TRUE。
扩展阅读
- Let's make the web faster.
- Using HTML 5 for performance improvements.
- Best Practices for Speeding Up Your Web Site.
- Steve Souders, In Search of Speed: slides, zip, video.
- Improving Netflix Performance @ Velocity 2008.
- Aaron Hopkins, Optimizing Page Load Time.
- SproutCore Blog, How SproutCore Makes Your App Run Faster.
- Caching Tutorial for Web Authors and Webmasters.
标签: frontend, optimization


0 条评论:
发表评论
订阅 博文评论 [Atom]
<< 主页