Skip to content

image.png

image.png

parse 解析 HTML(HTML ==> DOM 树 + CSSOM 树)

image.png

document DOM 树

拿到这个 HTML 字符串之后,为了方便操作,将 HTML 转换成对象(树结构),同时也提供了 js 操作 html 的能力

image.png

CSS DOM 树

css 也同理,会生成一个 css DOM 树,其中根节点是样式表 image.png

样式表:

  • 浏览器默认样式表(下面是 chrome 源码的默认样式表) image.png
  • <link xxx> 外部样式表`
  • <style> 内部样式表
  • stlye 内联样式表(行内样式表)

可以通过 document.styleSheets 获取网页下的所有样式表,也可以操作样式表

image.png

image.png

HTML 解析过程

HTML 解析遇到 CSS

为了提高解析效率,浏览器会启动一个预解析器先先下载和解析 CSS (生成 css DOM 还是交给渲染主线程,只是帮忙做一些解析工作),CSS 解析是在另一个线程上执行,因 此 CSS 不会阻塞 HTML 的解析

image.png

HTML 解析遇到 CSS 总结:

  • 解析过程中遇到 CSS 解析 CSS,遇到 JS 解析 JS。为了提高效率,浏览器在开始解析前 ,会启动一个预解析的线程,率先下载 HTML 中的外部 CSS 文件和外部的 JS 文件。
  • 如果主线程解析到 link 位置,此时外部的 CSS 文件还没有下载解析好,主线程不会 等待,继续解析后续的 HTML。这是因为 CSS 的下载和解析是在预解析线程中进行的。这 就是 CSS 不会阻塞 HTML 解析的根本原因

HTML 解析遇到 JS

  • 渲染主线程遇到 JS 时必须暂停一切行为,等待下载执行完后才能继续,因为 JS 代 码有可能会改动 HTML 上的 DOM 元素
  • 预解析线程可以分担一点下载 jS 任务

image.png

HTML 解析遇到 JS 总结:

  • 当主线程解析到 script 位置时,会停止解析 HTML,等待 JS 文件下载并将全局代码 解析执行完成后,才能继续解析 HTML。
  • 这是因为 JS 代码的执行过程可能会修改当前的 DOM 树,所以 DOM 树的生成必须暂停。 这就是 JS 阻塞 HTML 解析的根本原因。

HTML 解析完成后,会得到 DOM 树和 CSSOM 树,浏览器的默认样式、内部样式、外部 样式、行内样式都会包含在 CSSOM 树中。

style 样式计算 (DOM 树 + CSSOM 树 ==> Computed DOM 树)

image.png

image.png

获取所有计算好的样式 getComputedStyle

总结:

  • 在样式计算中,主线程会遍历得到的 DOM 树,依次为树中的每个节点计算出它最终的样 式,称之为 Computed Style
  • 在这一过程中,很多预设值会变成绝对值,比如 red 会变成 rgb(255,0,0),相对单 位会变成绝对单位,比如 em 会变成 px
  • 这一步完成后,会得到一颗带有样式的 DOM 树。

layout 布局 (Computed DOM 树 ==> 布局树)

image.png

image.png

总结:

  • 布局阶段会依次遍历 DOM 树(样式计算后得到的 DOM 树)的每一个节点,计算每个节点 的几何信息。例如节点的宽高,相对包含块的位置。
  • 大部分时候,DOM 树和布局树并非一一对应
  • 比如 display:none 的节点没有几何信息,因此不会生成到布局树,又比如使用了伪元 素选择器,虽然 DOM 树中不存在这些伪元素节点,但它拥有几何信息,所以会生成到布 局树中。还有匿名行盒、匿名块盒等等都会导致 DOM 树和布局树无法一一对应。

image.png

  • 比如说 <head><>link> 标签等,是被隐藏的,因为浏览器默认让它隐藏。

image.png

  • 获取布局树的信息,布局树并不是 DOM 树,它是其它的对象(浏览器 C++写的)
document.body.clientWidth

layer 分层 (布局树 ==> 分层)

image.png

总结:

  • 主线程会使用一套复杂的策略对整个布局树进行分层。
  • 分层的好处在于,将来某一个层改变后,仅会对该层进行后续处理,从而提高效率。
  • 滚动条、堆叠上下文、transform、opacity 等样式都会或多或少的影响分层结果,也可 以通过 will-change 属性告诉浏览器这个节点未来将会改变,从而更大程度的影响分 层结果。 image.png

paint 绘制 (分层后 ==> 为每一层生成如何绘制的指令)

image.png

渲染主线程做到这一步就到此为止,剩余工作交给其它线程完成:

image.png

总结:

  • 主线程会为每个层单独绘制指令集,用于描述这一层的内容该如何画出来。

tiling 分块 (每个图层 ==> 在合成线程中分成多个小区域)

image.png

合成线程也是渲染进程开启的 image.png

image.png

总结:

  • 完成绘制后,主线程将每个图层的绘制信息提交个合成线程,剩余工作将由合成线程完成 。
  • 合成线程首先对每个图层进行分块,将其划分为更多的小区域。
  • 它会从线程池中拿取多个线程来完成分块工作

raster 光栅化 (块 ==> 在 GPU 进程中将每个块变成位图)

image.png

image.png

image.png

总结:

  • 分块完成后,进入光栅化阶段。
  • 合成线程会将块信息交给 GPU 进程,以极高的速度完成光栅化。
  • GPU 进程会开启多个线程来完成光栅化,并且优先处理靠近视口区域的块。
  • 光栅化的结果,就是一块一块的位图。

draw 画 (位图 ==> GPU 最终呈现)

image.png

总结:

  • 合成线程拿到每个层、每个块的位图后,生成一个个指引(quad)信息。
  • 指引会标识出每个位图应该画到屏幕的哪个位置,以及会考虑到旋转、缩放等变形。变 形发生在合成线程中,与渲染主线程无关,这就是 transform 效率高的本质原因
  • 合成线程会把 quad 提交给 GPU 进程,由 GPU 进程产生系统调用,提交给 GPU 硬件, 完成最终的屏幕成像。

完整过程

渲染主线程:

HTML ==> DOM 树 + CSSOM 树 ==> Computed DOM 树(样式计算后) ==> 布局树 ==> 分层 ==> 绘制(为每一层生成如何绘制的指令)

合成线程:分块(每个图层 ==> 在合成线程中分成多个小区域)

GPU: 光栅化(块 ==> 在 GPU 进程中将每个块变成位图) ==> 画(最终呈现)

image.png

常见问题

什么是 reflow(回流)?

image.png

  • reflow 的本质就是改变了 CSSOM 树,进而改变了 Computed DOM 树(样式计算后生成的 树),导致需要重新计算 layout 树。
  • 为了避免连续的多次操作导致布局树的反复计算,浏览器会合并这些操作,当 JS 代码全 部完成后再进行统一计算。所以,改动属性造成的 reflow 是异步完成的
  • 为了避免 JS 获取布局信息无法及时获取最新的布局信息,浏览器会在获取属性时立即 reflow

什么是 repaint (重绘)?

image.png

  • 重绘的本质改变了计算样式树,但布局树未改动,就是重新根据分层信息计算了绘制指 令,比如只改变 dom 的可见样式(颜色)
  • 由于元素的布局信息也属于可见样式,所以 reflow 一定会引起 repaint。

为什么 transform 效率高?

image.png

  • 因为 transform 既不胡影响布局也不会影响绘制指令,它影响的只是渲染流程的最后 一个 draw 阶段
  • 由于 draw 阶段在合成线程中,所以 transform 的变化几乎不会影响渲染主线程。反之 ,渲染主线程无论如何忙碌,甚至卡死,也不会影响 transform 的变化。所以 transform 的效率高。