八、渲染篇 2:知己知彼——解锁浏览器背后的运行机制
平时我们几乎每天都在和浏览器打交道,在一些兼容任务比较繁重的团队里,苦逼的前端攻城师们甚至为了兼容各个浏览器而不断地去测试和调试,还要在脑子中记下各种遇到的 BUG 及解决方案。即便如此,我们好像并没有去主动地关注和了解下浏览器的工作原理。我想如果我们对此做一点了解,在项目过程中就可以有效地避免一些问题,并对页面性能做出相应的改进。
“知己知彼,百战不殆”,今天,我们就一起来揭开浏览器渲染过程的神秘面纱!
浏览器的“心”
- 浏览器的“心”,说的就是浏览器的内核。在研究浏览器微观的运行机制之前,我们首先要对浏览器内核有一个宏观的把握。
- 开篇提到许多工程师因为业务需要,免不了需要去处理不同浏览器下代码渲染结果的差异性。这些差异性正是因为浏览器内核的不同而导致的——浏览器内核决定了浏览器解释网页语法的方式。
浏览器内核可以分成两部分:渲染引擎(Layout Engine 或者 Rendering Engine)和 JS 引擎。早期渲染引擎和 JS 引擎并没有十分明确的区分,但随着 JS 引擎越来越独立,内核也成了渲染引擎的代称(下文我们将沿用这种叫法 )。渲染引擎又包括了 HTML 解释器、CSS 解释器、布局、网络、存储、图形、音视频、图片解码器等等零部件。

目前市面上常见的浏览器内核可以分为这四种:Trident(IE)、Gecko(火狐)、Blink(Chrome、Opera)、Webkit(Safari)。
这里面大家最耳熟能详的可能就是 Webkit 内核了。很多同学可能会听说过 Chrome 的内核就是 Webkit,殊不知 Chrome 内核早已迭代为了 Blink。但是换汤不换药,Blink 其实也是基于 Webkit 衍生而来的一个分支,因此,Webkit 内核仍然是当下浏览器世界真正的霸主。
下面我们就以 Webkit 为例,对现代浏览器的渲染过程进行一个深度的剖析。
开启浏览器渲染“黑盒”
什么是渲染过程?简单来说,渲染引擎根据 HTML 文件描述构建相应的数学模型,调用浏览器各个零部件,从而将网页资源代码转换为图像结果,这个过程就是渲染过程(如下图)。

从这个流程来看,浏览器呈现网页这个过程,宛如一个黑盒。在这个神秘的黑盒中,有许多功能模块,内核内部的实现正是这些功能模块相互配合协同工作进行的。其中我们最需要关注的,就是HTML 解释器 、CSS 解释器 、图层布局计算模块 、视图绘制模块 与JavaScript 引擎 这几大模块:
HTML 解释器:将 HTML 文档经过词法分析输出 DOM 树。
CSS 解释器:解析 CSS 文档, 生成样式规则。
图层布局计算模块:布局计算每个对象的精确位置和大小。
视图绘制模块:进行具体节点的图像绘制,将像素渲染到屏幕上。
JavaScript 引擎:编译执行 Javascript 代码。
浏览器渲染过程解析

有了对零部件的了解打底,我们就可以一起来走一遍浏览器的渲染流程了。在浏览器里,每一个页面的首次渲染都经历了如下阶段(图中箭头不代表串行,有一些操作是并行进行的,下文会说明):

- 解析 HTML
在这一步浏览器执行了所有的加载解析逻辑,在解析 HTML 的过程中发出了页面渲染所需的各种外部资源请求。
- 计算样式
浏览器将识别并加载所有的 CSS 样式信息与 DOM 树合并,最终生成页面 render 树(:after :before 这样的伪元素会在这个环节被构建到 DOM 树中)。
- 计算图层布局
页面中所有元素的相对位置信息,大小等信息均在这一步得到计算。
- 绘制图层
在这一步中浏览器会根据我们的 DOM 代码结果,把每一个页面图层转换为像素,并对所有的媒体文件进行解码。
- 整合图层,得到页面
最后一步浏览器会合并合各个图层,将数据由 CPU 输出给 GPU 最终绘制在屏幕上。(复杂的视图层会给这个阶段的 GPU 计算带来一些压力,在实际应用中为了优化动画性能,我们有时会手动区分不同的图层)。
几棵重要的“树”
上面的内容没有理解透彻?别着急,我们一起来捋一捋这个过程中的重点——树!
为了使渲染过程更明晰一些,我们需要给这些”树“们一个特写:

DOM 树:解析 HTML 以创建的是 DOM 树(DOM tree ):渲染引擎开始解析 HTML 文档,转换树中的标签到 DOM 节点,它被称为“内容树”。
CSSOM 树:解析 CSS(包括外部 CSS 文件和样式元素)创建的是 CSSOM 树。CSSOM 的解析过程与 DOM 的解析过程是并行的 。
渲染树:CSSOM 与 DOM 结合,之后我们得到的就是渲染树(Render tree )。
布局渲染树:从根节点递归调用,计算每一个元素的大小、位置等,给每个节点所应该出现在屏幕上的精确坐标,我们便得到了基于渲染树的布局渲染树(Layout of the render tree)。
绘制渲染树: 遍历渲染树,每个节点将使用 UI 后端层来绘制。整个过程叫做绘制渲染树(Painting the render tree)。
基于这些“树”,我们再梳理一番:
渲染过程说白了,首先是基于 HTML 构建一个 DOM 树,这棵 DOM 树与 CSS 解释器解析出的 CSSOM 相结合,就有了布局渲染树。最后浏览器以布局渲染树为蓝本,去计算布局并绘制图像,我们页面的初次渲染就大功告成了。
- 之后每当一个新元素加入到这个 DOM 树当中,浏览器便会通过 CSS 引擎查遍 CSS 样式表,找到符合该元素的样式规则应用到这个元素上,然后再重新去绘制它。
- 有心的同学可能已经在思考了,查表是个花时间的活,我怎么让浏览器的查询工作又快又好地实现呢?OK,讲了这么多原理,我们终于引出了我们的第一个可转化为代码的优化点——CSS 样式表规则的优化!
