前端性能优化最佳实践

作者:minwe
原文地址:https://csspod.com/frontend-performance-best-practices/

本文主要考量客户端性能、服务器端和网络性能,内容框架来自 Yahoo Developer Network,包含 7 个类别共 35 条前端性能优化最佳实践,在此基础上补充了一些相关或者更符合主流技术的内容。

同时,建议关注及时更新的 Google 性能优化指南。

  1. 页面内容
    --

1.1 减少 HTTP 请求数

Web 前端 80% 的响应时间花在图片、样式、脚本等资源下载上。浏览器对每个域名的连接数是有限制的,减少请求次数是缩短响应时间的关键。

通过简洁的设计减少页面所需资源,进而减少 HTTP 请求,这是最直接的方式,前提是你的 Boss、设计师同事不打死你。所以,还是另辟蹊径吧:

  • 合并 JavaScript、CSS 等文件;

  • 服务器端(CDN)自动合并

  • 基于 Node.js 的文件合并工具一抓一大把

  • 使用CSS Sprite:将背景图片合并成一个文件,通过background-imagebackground-position控制显示;

  • Sprite Cow

  • Spritebox

逐步被 Icon Font 和 SVG Sprite 取代。

  • Image Map:合并图片,然后使用坐标映射不同的区域(演示)。

缺点:仅适用于相连的图片;设置坐标过程乏味且易出错;可访性问题。不推荐使用这种过时的技术。

  • Inline Image:使用 Data URI scheme 将图片嵌入 HTML 或者 CSS 中。

会增加文件大小,也可能产生浏览器兼容及其他性能问题(有待整理补充)。

未来的趋势是使用内嵌 SVG。

1.2 减少 DNS 查询

用户输入 URL 以后,浏览器首先要查询域名(hostname)对应服务器的 IP 地址,一般需要耗费 20-120 毫秒 时间。DNS 查询完成之前,浏览器无法从服务器下载任何数据。

基于性能考虑,ISP、局域网、操作系统、浏览器都会有相应的 DNS 缓存机制。

  • IE 缓存 30 分钟,可以通过注册表中DnsCacheTimeout项设置;
  • Firefox 混存 1 分钟,通过network.dnsCacheExpiration配置;
  • (TODO:补充其他浏览器缓存信息)

首次访问、没有相应的 DNS 缓存时,域名越多,查询时间越长。所以应尽量减少域名数量。但基于并行下载考虑,把资源分布到 2 个域名上(最多不超过 4 个)。这是减少 DNS 查询同时保证并行下载的折衷方案。

1.3 避免重定向

HTTP 重定向通过301/302状态码实现。

HTTP/1.1 301 Moved Permanently  
Location: http://example.com/newuri  
Content-Type: text/html  

客户端收到服务器的重定向响应后,会根据响应头中Location的地址再次发送请求。重定向会影响用户体验,尤其是多次重定向时,用户在一段时间内看不到任何内容,只看到浏览器进度条一直在刷新。

有时重定向无法避免,在糟糕也比抛出 404 好。虽然通过 HTML meta refresh 和 JavaScript 也能实现,但首选HTTP 3xx跳转,以保证浏览器「后退」功能正常工作(也利于 SEO)。

  • 最浪费的重定向经常发生、而且很容易被忽略:URL 末尾应该添加/但未添加。比如,访问http://astrology.yahoo.com/astrology将被 301 重定向到http://astrology.yahoo.com/astrology/(注意末尾的/)。如果使用 Apache,可以通过Aliasmod_rewriteDirectorySlash解决这个问题。
  • 网站域名变更:CNAME 结合Aliasmod_rewrite或者其他服务器类似功能实现跳转。

1.4 缓存 Ajax 请求

Ajax 可以提高用户体验。但「异步」不意味着「及时」,优化 Ajax 响应速度提高性能仍是需要关注的主题。

最重要的的优化方式是缓存响应结果,详见 添加 Expires 或 Cache-Control 响应头。

以下规则也关乎 Ajax 响应速度:

  • 启用 Gzip
  • 减少 DNS 查询
  • 压缩 JavaScript 和 CSS
  • 避免重定向
  • 配置 Etag

1.5 延迟加载

页面初始加载时哪些内容是绝对必需的?不在答案之列的资源都可以延迟加载。比如:

  • 非首屏使用的数据、样式、脚本、图片等;
  • 用户交互时才会显示的内容。

遵循「渐进增强」理念开发的网站:JavaScript 用于增强用用户体验,但没有(不支持) JavaScript 也能正常工作,完全可以延迟加载 JavaScript。

1.6 预先加载

预先加载利用浏览器空闲时间请求将来要使用的资源,以便用户访问下一页面时更快地响应。

  • 无条件预先加载:页面加载完成(load)后,马上获取其他资源。以 google.com 为例,首页加载完成后会立即下载一个 Sprite 图片,此图首页不需要,但是搜索结果页要用到。
  • 有条件预先加载:根据用户行为预判用户去向,预载相关资源。比如 search.yahoo.com 开始输入时会有额外的资源加载。

Chrome 等浏览器的地址栏也有类似的机制。

  • 有「阴谋」的预先加载:页面即将上线新版前预先加载新版内容。网站改版后由于缓存、使用习惯等原因,会有旧版的网站更快更流畅的反馈。 为缓解这一问题,在新版上线之前,旧版可以利用空闲提前加载一些新版的资源缓存到客户端,以便新版正式上线后更快的载入(好一个「心机猿」:scream:)。

TODO: Prefetch 相关细节

  • Resource Hints Spec

1.7 减少 DOM 元素数量

复杂的页面不仅下载的字节更多,JavaScript DOM 操作也更慢。例如,同是添加一个事件处理器,500 个元素和 5000 个元素的页面速度上会有很大区别。

从以下几个角度考虑移除不必要的标记:

  • 是否还在使用表格布局?
  • 塞进去更多的
    仅为了处理布局问题?也许有更好、更语义化的标记。
  • 能通过伪元素实现的功能,就没必要添加额外元素,如清除浮动。

浏览器控制台中输入以下代码可以计算出页面中有多少 DOM 元素:

document.getElementsByTagName('*').length; 

对比标记良好的的网站,看看差距是多少。

为什么不使用表格布局?

  • 更多的标签,增加文件大小;
  • 不易维护,无法适应响应式设计;
  • 性能考量,默认的表格布局算法会产生大量重绘(参见表格布局算法)。

1.8 划分内容到不同域名

浏览器一般会限制每个域的并行线程(一般为 6 个,甚至更少),使用不同的域名可以最大化下载线程,但注意保持在 2-4 个域名内,以避免 DNS 查询损耗。

例如,动态内容放在csspod.com上,静态资源放在static.csspod.com上。这样还可以禁用静态资源域下的 Cookie,减少数据传输,详见 Cookie 优化。

更多信息参考 Maximizing Parallel Downloads in the Carpool Lane

1.9 尽量减少 iframe 使用

使用 iframe 可以在页面中嵌入 HTML 文档,但有利有弊。