前端性能优化—-雅虎 35 条军规

  1. 减少 HTTP 请求

    80%的终端响应时间主要花费在前端,而这部分时间主要在下载网页中的图片,样式文件,脚本文件,flash 等资源。减少这些资源的数量就相应地减少了用于渲染页面的 HTT 请求数量。以下是一些具体的方法

  • 简化设计

  • 打包文件,将多个 JS 文件打包成一个 JS 文件,同样地,将多个 CSS 文件打包成一个 CSS 文件

  • 使用 CSS Sprites,将页面中用到的背景小图标并合成一张图片,再使用 CSS 中的bakground-imagebackground-position来显示所需图像部分

  • 图像映射,html 标签,可以创建可点击的图像区域,具体参考w3school或者 W3C 文档

  • 内嵌图片,通过data:URL scheme内嵌

  1. 使用 CDN

    在现有的网络中增加一层新的网络架构,将网站的内容发布到最接近用户的 Cache 服务器内,通过 DNS 负责均衡技术,判断用户来源就近访问 Cache 服务器上所需的内容。如此便可以减少数据在网络上传输的时间,提高速度

  2. 设置头文件过期时间或者缓存策略

    • 静态内容:将 Expires 响应头设置为将来很远的时间,实现「永不过期」策略;
    • 动态内容:设置合适的 Cache-Control 响应头,让浏览器有条件地发起请求。
  3. Gzip压缩

    • Gzip压缩通常可以减少 70%的响应大小,对某些文件更可能高达 90%,比Deflate更高效

    • 主流 Web 服务器都有相应模块,而且绝大多数浏览器支持gzip解码

    • 图片和 PDF 文件不要使用 gzip,它们本身已经压缩过,再使用gzip 压缩不仅浪费 CPU 资源,而且还可能增加文件体积

    • 从 HTTP/1.1 开始,web 客户端就有了支持压缩的 Accept-Encoding HTTP 请求头

  4. 把 CSS 文件放在顶部

    把样式表放在<head>中可以让页面渐进渲染,尽早呈现视觉反馈,给用户加载速度很快的感觉。

  5. 把 JS 文件放在底部

    • 浏览器下载脚本时,会阻塞其他资源并行下载,即使是来自不同域名的资源。因此,最好将脚本放在底部,以提高页面加载速度。

    • 一些特殊场景无法将脚本放到页面底部的,可以考虑<script>的以下属性:

    • defer 属性;

    • HTML5 新增的async属性。

  6. 避免 CSS 表达式

    CSS 表达式可以在 CSS 里执行 JavaScript,仅 IE5-IE7 支持,IE8 标准模式已经废弃。 CSS 表达式超出预期的频繁执行,页面滚动、鼠标移动时都会不断执行,带来很大的性能损耗。

  7. 将 JS 和 CSS 外链

    外部 JavaScript 和 CSS 文件可以被浏览器缓存,在不同页面间重用,也能降低页面大小。

  8. 减少 DNS 查询

    • 用户输入 URL 以后,浏览器首先要查询域名(hostname)对应服务器的 IP 地址,一般需要耗费 20-120 毫秒时间。DNS 查询完成之前,浏览器无法从服务器下载任何数据。
    • 基于性能考虑,ISP、局域网、操作系统、浏览器都会有相应的 DNS 缓存机制。 IE 缓存 30 分钟,可以通过注册表中 DnsCacheTimeout 项设置;
      Firefox 缓存 1 分钟,通过 network.dnsCacheExpiration 配置;
  9. 减小 JSS 和 CSS 体积

    • 压缩代码可以移除非功能性的字符(注释、空格、空行等),减少文件大小,提高载入速度

    • 开源社区有很多前端优化工具,比如:cssnano , JSMin
      , UglifyJS

    • Gulp, Webpack等流行构建工具都有相应的支持

  10. 避免重定向

    • 客户端收到服务器的重定向响应后,会根据响应头中 Location 的地址再次发送请求。重定向会影响用户体验,尤其是多次重定向时,用户在一段时间内看不到任何内容,只看到浏览器进度条一直在刷新。
    • 最浪费的重定向经常发生、而且很容易被忽略:URL 末尾应该添加/
      但未添加。比如,访问http://astrology.yahoo.com/astrology将被301重定向到 http://astrology.yahoo.com/astrology/(注意末尾的 /)。如果使用
      Apache,可以通过Aliasmod_rewriteDirectorySlash解决这个问题。
    • 网站域名变更:CNAME结合Aliasmod_rewrite或者其他服务器类似功能实现跳转。
  11. 移除重复脚本

    重复的脚本不仅产生不必要的 HTTP 请求,而且重复解析执行浪费时间和计算资源。

  12. 配置ETags

    ETags通过文件版本标识,方便服务器判断请求的内容是否有更新,如果没有就响应 304,避免重新下载

  13. 缓存 Ajax

    有尚未过期的 Expires 或者 Cache-Control HTTP 头,那么之前的资源就可以从缓存中读出。必须通知浏览器,应该继续使用之前缓存的资源响应,还是去请求一个新的。可以通过给资源的 Ajax
    URL 里添加一个表明用户资源最后修改时间的时间戳来实现。如果资源从上一次下载之后再没有被修改过,时间戳不变,资源就将从浏览器缓存中直接读出,从而避免一次额外的 HTTP 往返消耗。具体参考[3. 设置头文件过期时间或者缓存策略](#3.
    设置头文件过期时间或者缓存策略)

  14. 尽早释放缓冲

    用户请求页面时,服务器通常需要花费 200 ~ 500 毫秒来组合 HTML 页面。在此期间,浏览器处于空闲、等待数据状态。使用 PHP 中的 flush()函数,可以发送部分已经准备好的
    HTML 到浏览器,以便服务器还在忙于处理剩余页面时,浏览器可以提前开始获取资源。

  15. 用 GET 方式进行 Ajax 请求

    浏览器执行 POST 请求时分成两步,先发送 Http Header,再发送 data。而 GET 只使用一个 TCP 数据包(Http Header 与 data)发送数据,所以首选 GET 方法。

    根据 HTTP 规范,GET 用于获取数据,POST 则用于向服务器发送数据,所以 Ajax 请求数据时使用 GET 更符合规范。

  16. 预加载组件

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

  17. 延迟加载组件

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

    • 非首屏使用的数据、样式、脚本、图片等
    • 用户交互时才会显示的内容
  18. 减少 DOM 元素数量

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

    • 是否还在使用表格布局?
    • 塞进去更多的
      仅为了处理布局问题?也许有更好、更语义化的标记。
    • 能通过伪元素实现的功能,就没必要添加额外元素,如清除浮动。 浏览器控制台中输入以下代码可以计算出页面中有多少 DOM 元素:
    1
    document.getElementsByTagName("*").length;
    • 为什么不使用表格布局?
      1
      2
      3
      + 更多的标签,增加文件大小;
      + 不易维护,无法适应响应式设计;
      + 性能考量,默认的表格布局算法会产生大量重绘
  19. 跨域分离组件

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

  20. 减少iframe数量

    • iframe可以把一个 HTML 文档插入到父文档里,重要的是明白iframe是如何工作的并高效地使用它。
    • iframe的优点:
      1
      2
      3
      + 可以用来加载速度较慢的第三方资源,如广告、徽章;
      + 可用作安全沙箱;
      + 可以并行下载脚本。
    • iframe的缺点:
      1
      2
      3
      4
      + 加载代价昂贵,即使是空的页面;
      + 阻塞页面 load 事件触发;
      + `iframe` 完全加载以后,父页面才会触发 load 事件。 Safari、Chrome 中通过 JavaScript 动态设置 iframe src 可以避免这个问题。
      + 缺乏语义。
  21. 不要出现 404 页面

    HTTP 请求很昂贵,返回无效的响应(如 404 未找到)完全没必要,降低用户体验而且毫无益处

  22. 减小 Cookie

    Cookie 被用于身份认证、个性化设置等诸多用途。Cookie 通过 HTTP 头在服务器和浏览器间来回传送,减少 Cookie 大小可以降低其对响应速度的影响。

    1
    2
    3
    4
    - 去除不必要的 Cookie;
    - 尽量压缩 Cookie 大小;
    - 注意设置 Cookie 的 domain 级别,如无必要,不要影响到 sub-domain;
    - 设置合适的过期时间。
  23. 对静态资源使用无 Cookie 的域名

    静态资源一般无需使用 Cookie,可以把它们放在使用二级域名或者专门域名的无 Cookie 服务器上,降低 Cookie 传送的造成的流量浪费,提高响应速度。

  24. 较少 DOM 访问次数

    • JavaScript 操作操作 DOM 很慢,尤其是 DOM 节点很多时。

    • 使用时应该注意:

    1
    2
    3
    4
    - 缓存已经访问过的元素;
    - 使用DocumentFragment暂存DOM,整理好以后再插入DOM树;
    - 操作className,而不是多次读写style;
    - 避免使用JavaScript修复布局。
  25. 开发高效的事件处理句柄

    • 减少绑定事件监听的节点,如通过事件委托;
    • 尽早处理事件,在 DOMContentLoaded 即可进行,不用等到 load 以后。
  26. 使用<link>而非@import

  27. 避免使用过滤器

    避免使用 AlphaImageLoader,可以使用 PNG8 替代

  28. 优化图片

  29. 优化 CSS Sprites

  • 水平排列Sprite中的图片,垂直排列会增加图片大小;
  • Sprite中把颜色较近的组合在一起可以降低颜色数,理想状况是低于 256 色以适用 PNG8 格式;
  • 不要在Sprite的图像中间留有较大空隙。减少空隙虽然不太影响文件大小,但可以降低用户代理把图片解压为像素图的内存消耗,对移动设备更友好。
  1. 不要再 HTML 中伸缩图片

    不要使用的 width、height 缩放图片,如果用到小图片,就使用相应大小的图片

  2. 缩小favicon.ico的大小并使用缓存

    Favicon.ico 一般存放在网站根目录下,无论是否在页面中设置,浏览器都会尝试请求这个文件。所以确保这个图标:

    • 存在(避免 404);
    • 尽量小,最好小于 1K;
    • 设置较长的过期时间。
    • 对于较新的浏览器,可以使用 PNG 格式的 favicon。
  3. 保证组件在 25K 以下

    这个限制是因为 iPhone 不能缓存大于 25K(压缩前)的组件

  4. 将组件打包进一个多部分的文档中

把各个组件打包成一个像有附件的电子邮件一样的复合文档里,可以用一个 HTTP 请求获取多个组件(记住一点:HTTP 请求是代价高昂的)。用这种方式的时候,要先检查用户代理是否支持(iPhone 就不支持)

  1. 避免空的图像src属性

    • 虽然 src 属性为空字符串,但浏览器仍然会向服务器发起一个 HTTP 请求
    • 空的 href 属性也存在类似问题