巨详细的浏览器渲染原理总结

113 阅读3分钟

浏览器渲染

Parser

解析 HTML 文档,构建 DOM 树,CSSOM。

JavaScript 编译

如果遇到 script,则会暂停解析 DOM。JavaScript 被解析为抽象语法树,传入解释器中被转为字节码,输出在主线程上运行。DOM 解析也是运行在主线程之上,于是 JavaScript 会阻塞 DOM 解析。

为什么 DOM 操作很慢

  1. DOM 本质是 C++ 对象,转化为 JavaScript 对象有一个包装的过程
  2. 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

  1. 从 DOM 树的根节点开始遍历每个可见的节点。(不可见节点比如:script,head)
  2. 对于每个 visible node,找到适配的 CSSOM 并应用。如果设置了 display: none,则不会被放入 Layout Tree
  3. Emit visible nodes,连带其内容和样式。(一时找不到比 emit 更好的中文词语)

至此 Layout Tree 构建完毕,其中包含了所有的可见内容和样式信息。

reflow

元素的大小、位置发生变化时,需要重新构建 Layout Tree,并重新布局计算。这个过程叫做 reflow

Paint

主线程将Layout Tree中的节点分层并构建 layer tree,创建绘制记录,将其发送给 Compositor thread。

分层

如果一个元素没有自己的层(layer),那么这个节点就属于父节点的图层。

提升为单独图层的条件:

  1. 拥有层叠上下文属性。z-index opacity position...
  2. 剪裁

repaint

元素的颜色等属性发生变化时,需要重新生成绘制记录。这个过程叫做 repaint

Compositing

合成器线程将图层分为图块,并判断这些分块是否需要被光栅化。之后将需要光栅化的图块发送给光栅化线程,待所有的图块光栅化完毕之后告知浏览器,浏览器将合成帧发送给 GPU 进程,显示页面。

分块机制

有的图层很大,但是用户看到的只是视口的那一部分,将图层分块渲染可以避免不必要的性能开销。

光栅化 Raster

光栅化线程将图块转成位图(使用像素阵列表示的图像,编码方式包括 RGB 等),存储在显存中。

光栅化会使用 GPU 加速,因此光栅化需要与 GPU 进程通信。

合成动画为什么如此丝滑?

  1. 合成动画被合成器线程控制,无关主线程就不会被其他主线程任务阻塞
  2. 不会引发回流重绘,减少性能开销
  3. 合成层的移动不用重新光栅化(因为在光栅化之前会判断是否需要光栅化,不需要光栅化则直接调用之前的缓存)