【前端性能优化指南】3 - 如何加速页面解析与处理?

🔙 上一篇 - 如何加快请求速度?javascript

在上一站中,咱们简单介绍了服务端处理与响应,到目前为止咱们已经经历了不少环节,也已经有了许多「性能优化的武器」。像是css

  • 利用各级缓存进行优化
  • 提早进行 DNS 查询或创建链接等方式加速请求
  • 在服务端避免没必要要的耗时
  • ……

不过,不要掉以轻心,后续仍然有大量的工做等待咱们来优化。下面就到了客户端接收响应的阶段了。html

主要工做

这一阶段浏览器须要处理的东西不少,为了更好地理解性能优化,咱们主要将其分为几个部分:前端

  • 页面 DOM 的解析;
  • 页面静态资源的加载,包括了页面引用的 JavaScript/CSS/图片/字体等;
  • 静态资源的解析与处理,像是 JavaScript 的执行、CSSOM 的构建与样式合成等;

大体过程就是解析页面 DOM 结构,遇到外部资源就加载,加载好了就使用。可是因为这部分的内容比较多,因此在这一节里咱们重点关注页面的解析(其余部分在写一节中介绍)。java

1. 注意资源在页面文档中的位置

咱们的目标是收到内容就尽快解析处理,页面有依赖的资源就尽快发送请求,收到响应则尽快处理。然而,这个美好的目标也有可能会被咱们不当心破坏。git

JavaScript 脚本和 CSS 样式表在关于 DOM 元素的属性,尤为是样式属性上都有操做的权利。这就像是一个多线程问题。服务端多线程编程中常常经过锁来保证线程间的互斥。回到我们的前端,如今也是两方在竞争同一个资源,显然也是会有互斥的问题。这就带来了 DOM 解析、JavaScript 加载与执行、CSS 加载与使用之间的一些互斥关系。github

仅仅看 DOM 与 CSS 的关系,则以下图所示:算法

pipeline for dom and css

HTML 解析为 DOM Tree,CSS 解析为 CSSOM,二者再合成 Render Tree,并行执行,很是完美。然而,当 JavaScript 入场以后,局面就变了:编程

pipeline for dom and css with js

根据标准规范,在 JavaScript 中能够访问 DOM。所以当遇到 JavaScript 后会阻塞 DOM 的解析。于此同时,为避免 CSS 与 JavaScript 之间的竞态,CSSOM 的构建会阻塞 JavaScript 的脚本执行。总结起来就是 ——浏览器

JavaScript 会阻塞 DOM 构建,而 CSSOM 的构建又回阻塞 JavaScript 的执行。

因此这就是为何在优化的最佳实践中,咱们基本都推荐把 CSS 样式表放在 <head> 之中(即页面的头部),把 JavaScript 脚本放在 <body> 的最后(即页面的尾部)。

关于这部分的一些解释能够看这篇文章[1]

2. 使用 defer 和 async

上面提到了,当 DOM 解析遇到 JavaScript 脚本时,会中止解析,开始下载脚本并执行,再恢复解析,至关因而阻塞了 DOM 构建。

那除了将脚本放在 body 的最后,还有什么优化方法么?是有的。

可使用 deferasync 属性。二者都会防止 JavaScript 脚本的下载阻塞 DOM 构建。可是二者也有区别,最直观的表现以下:

async & defer

defer 会在 HTML 解析完成后,按照脚本出现的次序再顺序执行;而 async 则是下载完成就当即开始执行,同时阻塞页面解析,不保证脚本间的执行顺序。

根据它们的特色,推荐在一些与主业务无关的 JavaScript 脚本上使用 async。例如统计脚本、监控脚本、广告脚本等。这些脚本通常都是一份独立的文件,没有外部依赖,不须要访问 DOM,也不须要有严格的执行时机限制。在这些脚本上使用 async 能够有效避免这些非核心功能的加载影响页面解析速度。

3. 页面文档压缩

HTML 的文档大小也会极大影响响应体下载的时间。通常会进行 HTML 内容压缩(uglify)的同时,使用文本压缩算法(例如 gzip)进行文本的压缩。关于资源压缩这一块,在下一节的内容中还会再详细进行介绍。


说一句题外话,你知道与页面解析密切相关的 DOMContentLoaded 事件什么时候触发么?interactive/complete 等 readyState 具体表明什么么?若是不太了解能够从HTML spec[2]里看。

用原话来讲就是:

Returns "loading" while the Document is loading, "interactive" once it is finished parsing but still loading subresources, and "complete" once it has loaded.

The readystatechange event fires on the Document object when this value changes.

The DOMContentLoaded event fires after the transition to "interactive" but before the transition to "complete", at the point where all subresources apart from async script elements have loaded.


好了,在这一站咱们又了解了页面的解析过程及其优化。

正如开头所说,其实解析页面、加载资源、使用资源是三个紧密相关的过程。在这里咱们主要着眼于页面的解析,而在「前端性能优化之旅」的下一站,咱们则会一块儿来涉足到这部分的其余诸多优化点中。

下一站 - 页面静态资源[TODO] 🔜


「性能优化」系列内容

  1. 带你全面掌握前端性能优化 🚀

  2. 如何利用缓存减小远程请求?

  3. 如何加快请求速度?

  4. 如何加速页面解析与处理?(本文)

  5. 静态资源优化的整体思路是什么?

    5.1. 如何针对 JavaScript 进行性能优化?

    5.2. 如何针对 CSS 进行性能优化?

    5.3. 图片虽好,但也会带来性能问题

    5.4. 字体也须要性能优化么?

    5.5. 如何针对视频进行性能优化?

  6. 如何避免运行时的性能问题?

  7. 如何经过预加载来提高性能?

  8. 尾声

目前内容已所有更新至 ✨ fe-performance-journey ✨ 仓库中,陆续会将内容同步到掘金上。若是但愿尽快阅读相关内容,也能够直接去该仓库中浏览。


参考资料

  1. Deciphering the Critical Rendering Path
  2. HTML5 spec: current-document-readiness
  3. Async Defer — JavaScript Loading Strategies
  4. Speed up Google Maps(and everything else) with async & defer
  5. HTML5 spec: parse HTML (the end)
相关文章
相关标签/搜索