[关闭]
@yangfch3 2017-01-16T14:07:54.000000Z 字数 3108 阅读 2827

页面性能之一:关键呈现路径

FE HTML+CSS JavaScript


关键呈现路径

一个页面的呈现需要经历

  1. 构建 DOM 树(Parse HTML
    • 构建 DOM 树时可能遇到脚本代码被阻塞(Evaluate Script
  2. 构建 CSSOM 数(Recalculate Style
  3. 合并 DOM 树与 CSSOM 树为 Render 树
  4. 布局——计算 尺寸、位置、定位Layout
  5. 绘制——像素渲染,样式、颜色、背景、边框(Paint
  6. 合成——GPU 合成各层(Composite

这个过程又叫做关键呈现路径,关键呈现路径的每一步都是自变量,影响着最终页面呈现的耗时。而我们对页面呈现表现的优化的出发点也是上面的几个过程,尽量减少呈现路径每一步的耗时,以优化页面的表现。

  1. 确保每一步尽快地完成,尤其是第一步、第二步,是整个页面呈现的根基
  2. 尽可能地避免阻塞

同时借助上面总结的过程,我们也很容易分析我们在页面稳定后的一些操作带来的资源开销大小,比如有些操作会带来 DOM 树的改变、重新布局、重新绘制,有些操作则只会带来 CSSOM 树的改变、重新绘制。现在,我们也就很容易明白回流重绘的差异,明白如何优化我们的操作从而减少资源的开销了。

构建 DOM Tree

image_1b1ebi4kr144k1o2a2g012dk1b469.png-96.9kB
上图很好地展示了从磁盘或网络中获取的 HTML 文档到最终的 DOM 树的整个过程。

  1. 转换(Bytes -> Characters):浏览器从磁盘或网络读取 HTML 的原始字节,然后根据指定的文件编码格式(例如 UTF-8)将其转换为相应字符。
  2. 符号化(Characters -> Tokens):浏览器将字符串转换为 W3C HTML5 标准 指定的各种符号 - 比如 ""、"" 及其他「尖括号」内的字符串。每个符号都有特殊含义并一套规则。
  3. 词法分析(Tokens -> Nodes):发射的符号转换为「对象」,定义它们的属性与规则。
  4. DOM 构建(Nodes -> DOM):最后,因为 HTML 标记定义不同标签间的相互关系(某些标签嵌套在其他标签中),所以创建的对象在树状数据结构中互相链接,树状数据结构还捕获原始标记中定义的父子关系:比如 HTML 对象是 body 对象的父对象,body 是 paragraph 对象的父对象等等。

构建 DOM 树在一些大型页面上的耗时较多,且很容易成为页面性能的瓶颈,在 Timeline 面板构建 DOM 树对应着 Parse HTML 事件(阶段)。

image_1b1ept2no1fmd1430vdu8qsv109.png-32.2kB

构建 CSSOM Tree

光有 DOM 树还是无法绘制得到我们看到的页面,我们还需要 CSSOM(CSS 对象模型)。CSSOM 构建“原料”的来源有:外部 CSS 文件(包括 import 的样式表)、内部 style 标签、内联样式(与标签写在一起)。

用户代理为每一个 HTML 元素准备了一套预设样式,我们的样式是对用户代理样式的覆盖。

正因为我们最终渲染绘制需要依赖 CSSOM,所以 CSSOM 的构建是阻塞渲染的。

构建 CSSOM 树在浏览器中的称呼是:Recalculate Style

叫做 Recalculate 是因为我们样式其实是在预设样式的基础上的重新计算。

image_1b1equ5l5m0cl5irp3qin16fk23.png-29kB

Recalculate Style

image_1b1eqme1f9uardbcmq14un196cm.png-10.3kB

CSSOM 的构建过程


非完整的 CSSOM 树(没有加上用户代理预设样式)

构建 Render Tree

CSSOM 树与 DOM 树融合成一棵 Render 树(渲染树),渲染树包括着渲染页面需要的节点。
image_1b1er367sanp1jv1ane1frkjgl2g.png-116.2kB

包括渲染页面需要的节点的言外之意便是:并非包括所有的 DOM 节点,像 head, script, meta 这样的不可见节点,以及 display: none 的节点不会在 Render 树上出现。

布局

有了 Render 树之后,就会进入布局阶段。

通过 Render 树我们能知道一个元素的表现是怎样的,但是我们还却一样东西:元素在页面何处?元素尺寸如何?这个就是布局阶段要做的了:计算位置尺寸

image_1b1erjs371veo115o1oqj1ikq1bp42t.png-34.6kB

Layout in Timeline

绘制

一旦布局(Layout)步骤完成,浏览器便发布 “Paint Setup” 与 “Paint” 事件,执行 paint 操作,将 渲染树 + 布局信息 转化为屏幕上的实际像素。

合成

在布局(layout)和绘制(paint)之后,浏览器会将多个复合层传入 GPU,进行合成工作(之前的布局与绘制都需要 CPU 的复杂计算)。

image_1b6johgjb2gr1n3td7bet2g4p9.png-23.5kB

浏览器层、GPU 层与展示层

对于 JavaScript 动画带来的重新布局、重新绘制和重新合成及其优化请移步另外一篇文章。


阻塞

既然我们已经知道了一个页面如何呈现,那么我们现在就来分析一下这些步骤里面那些可能带来流程的阻塞吧。

我们页面呈现的主线是 DOM 树的构建(Parse HTML),支线是 CSSOM 树的构建,同时还会有脚本的执行插入主线。一旦主线、支线汇合,形成 Render 树,便可顺利地布局、绘制。

因此,我们很容易得出初步的想法:以下这些因素影响着呈现的耗时

  1. DOM 树构建的耗时
  2. DOM 树构建过程中遇到 JavaScript 代码,无异步说明的话会搁置 DOM 树的构建并立即执行脚本代码(其实是有原因的,见 3),阻塞了 DOM 树的构建
  3. 因为 JavaScript 代码可能会牵涉到元素样式的操作(读、写),所以 JavaScript 的执行又依赖脚本前面 CSSOM 树的构建完毕,也就是说 CSSOM 树的构建阻塞了 JavaScript 的立即执行

    HTML 5 规范指出:

    1. 脚本执行前,出现在当前<script>之前的<link rel="stylesheet">必须完全载入。
    2. 脚本执行会阻塞DOM解析。

依赖阻塞线:

(pre)CSSOM ---Blocking----> Evaluate Script -----Blocking-----> DOM

image_1b19asj7e1aafou7u0m1e761fia9.png-7667.7kB

无特殊异步处理下的阻塞示意图

最后,请注意:我们的图片并不会阻止 DOMContentLoaded 事件! 这证明,我们无需等待网页上的每个资源即可构建呈现树甚至绘制网页:不是所有资源均对提供首次描绘起重要作用。事实上,就像我们将要看到的,我们谈论关键呈现路径时,通常谈论的是 HTML 标记、CSS 和 JavaScript。图片不会阻止网页的首次呈现,尽管如此,我们也应努力确保系统尽快绘制图片!

onload 事件标记的点是 网页所需的所有资源均已下载并经过处理 的点,图片资源的加载会影响到 window 的 onload 时间点

异步与延迟

引入外部脚本的 script 标签还支持两个属性:

  1. async:立即下载脚本,异步执行
  2. defer:立即下载脚本,延迟到 DOM 树构建完成时执行

根据上文对阻塞的分析,我们对于脚本可以采取以下措施:

  1. 无特殊情况,脚本放在文档底部
  2. 对 DOM 构建时机无特殊依赖的外部脚本可以采用异步,这样节约的时间是:脚本下载的时间

评估页面表现

image_1b6jot81t46ujqi11ev1n0i184fm.png-183.6kB

链接

考虑网络后的关键呈现路径

链接

优化关键呈现路径

优化关键呈现路径常规步骤:

  1. 分析和描述关键路径:资源数量、字节数、长度。
  2. 尽量减少关键资源数量:删除相应资源、延迟下载、标记为异步资源等等。
  3. 优化剩余关键资源的加载顺序:您需要尽早下载所有关键资源,以缩短关键路径长度。
  4. 尽量减少关键字节数,以缩短下载时间(往返次数)。

其他提升页面呈现速度的建议:

  1. 推荐使用异步 JavaScript 资源
  2. 延迟解析 JavaScript
  3. 避免运行时间长的 JavaScript
  4. 将 CSS 放入文档的 head 标签内
  5. 避免使用 CSS import
  6. 内联、内部化阻止呈现的 CSS
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注