浏览器渲染
Parser
解析 HTML 文档,构建 DOM 树,CSSOM。
JavaScript 编译
如果遇到 script,则会暂停解析 DOM。JavaScript 被解析为抽象语法树,传入解释器中被转为字节码,输出在主线程上运行。DOM 解析也是运行在主线程之上,于是 JavaScript 会阻塞 DOM 解析。
为什么 DOM 操作很慢
- DOM 本质是 C++ 对象,转化为 JavaScript 对象有一个包装的过程
- JavaScript 执行与 DOM 渲染共用主线程,频繁的上下文切换会产生大量的性能开销
async defer
为 script 标签设置 async defer 属性,可以避免 JavaScript 阻塞 DOM。
<script async src="..."/>
<script defer src="..."/>
-
async: 异步加载 JavaScript,加载完成之后立刻执行。这样不能完全避免阻塞 DOM,并且当出现多个 script 标签时,无法保证执行顺序。
-
defer: 异步加载 JavaScript,待 DOM 解析完毕之后会按顺序执行。并且 DOMContentLoaded 会在 JavaScript 执行完毕之后触发。
Layout
主线程通过 DOM 树与 CSSOM 树构建 Layout Tree,之后进行布局计算得出每个元素的坐标,把结果放入 Layout Tree中。
Layout Tree
- 从 DOM 树的根节点开始遍历每个可见的节点。(不可见节点比如:script,head)
- 对于每个 visible node,找到适配的 CSSOM 并应用。如果设置了
display: none,则不会被放入Layout Tree - Emit visible nodes,连带其内容和样式。(一时找不到比 emit 更好的中文词语)
至此 Layout Tree 构建完毕,其中包含了所有的可见内容和样式信息。
reflow
元素的大小、位置发生变化时,需要重新构建 Layout Tree,并重新布局计算。这个过程叫做 reflow
Paint
主线程将Layout Tree中的节点分层并构建 layer tree,创建绘制记录,将其发送给 Compositor thread。
分层
如果一个元素没有自己的层(layer),那么这个节点就属于父节点的图层。
提升为单独图层的条件:
- 拥有层叠上下文属性。z-index opacity position...
- 剪裁
repaint
元素的颜色等属性发生变化时,需要重新生成绘制记录。这个过程叫做 repaint
Compositing
合成器线程将图层分为图块,并判断这些分块是否需要被光栅化。之后将需要光栅化的图块发送给光栅化线程,待所有的图块光栅化完毕之后告知浏览器,浏览器将合成帧发送给 GPU 进程,显示页面。
分块机制
有的图层很大,但是用户看到的只是视口的那一部分,将图层分块渲染可以避免不必要的性能开销。
光栅化 Raster
光栅化线程将图块转成位图(使用像素阵列表示的图像,编码方式包括 RGB 等),存储在显存中。
光栅化会使用 GPU 加速,因此光栅化需要与 GPU 进程通信。
合成动画为什么如此丝滑?
- 合成动画被合成器线程控制,无关主线程就不会被其他主线程任务阻塞
- 不会引发回流重绘,减少性能开销
- 合成层的移动不用重新光栅化(因为在光栅化之前会判断是否需要光栅化,不需要光栅化则直接调用之前的缓存)