本文内容是极客时间《浏览器工作原理与实践》专栏的读书笔记,均是基于 Chrome 浏览器,默认读者已经有一定的前端基础。

浏览器架构演进

早起浏览器使用的是单进程架构,即所有的工作都运行在同一个进程里面,这样的架构有诸多弊病。比如一些复杂的 js 代码引起渲染模块的崩溃,在任意时刻同时只能有一个模块在运行,另外也存在一些安全性的问题,因为只有一个进程,那么其它插件就能访问到该进程的所有资源。遇到的问题就是不安全、不流畅、不稳定。

为了解决这些问题,浏览器架构逐渐向多进程架构发展,早起浏览器用了浏览器主进程、插件进程、渲染进程共三个进程。因为进程之间是相互隔离的,所以一个插件的崩溃就不会再导致其它页面的崩溃了,同样的道理也不会影响到其它页面的流畅度。

目前的浏览器将网络进程和 GPU 进程从浏览器主进程中分离出来,所以加上渲染进程和插件进程就有 5 个进程了,而插件进程并不是每个页面都一定需要的,所以用 Chrome 打开一个页面时,那至少就会出现四个进程。

当然这里的进程数是不一定的,比如同一个站点的页面或者在页面中使用了 iframe 等等情况都会影响进程数量。这里多说一嘴,虽然浏览器采用了多进程架构,但是同一个站点可能会使用相同的渲染进程,所以有时也会遇到一个页面崩溃,导致其它所有页面一起崩溃的情况。

毫无疑问,多进程架构虽然有诸多优点,但是也会占用很多的资源,同时各个模块之间的耦合度较高,变得更难于适应新需求,为了解决这些问题,Chrome 官方团队在 2016 年使用「面向服务架构」的设计思想设计了新的 Chrome 架构。也就是说 Chrome 整体架构会朝向现代操作系统所采用的「面向服务的架构」来发展,原来的各个模块会被重构成独立的服务,每个服务都可以在独立的进程中运行。

浏览器基本原理 - 图1

同时 CHrome 还提供了灵活的弹性架构,在强大性能设备上会以多进程的方式运行基础服务,但是在占用资源受限的设备上会将多个服务整合到一个进程中,从而节省内存的占用。

浏览器渲染过程

渲染机制比较复杂,所以渲染过程会被分为很多子阶段,HTML、CSS、JS 经过这些子阶段最终输出位像素,我们把这个流程称之为渲染流水线。按照时间顺序可以把流水线分为构建 DOM 树、样式计算、布局阶段、分层、绘制、分块、光栅化和合成等阶段,每个阶段都有相应的输入、输出内容。

由于浏览器无法直接理解 HTML,所以需要将 HTML 转换为 DOM 树,在开发者工具中我们能看到 DOM 树的结构,可以看到 DOM 树和 HTML 内容基本是一模一样的,但是 DOM 是保存在内存中的树状结构,可以通过 js 查询、修改其中的内容。

浏览器基本原理 - 图2

DOM 树已经构建出来了,那么下一步就是要为 DOM 节点生成对应的样式了,与 HTML 一样,浏览器也无法直接理解 CSS,所以需要将 CSS 转换为浏览器能理解的样式表(
styleSheets),在控制台输入document.styleSheets就能看到页面所使用的样式了,这个样式表把通过 link 引入的 css 文件、<style>标记内的 css 和元素 style 属性内嵌的 css 都包含进去了。

将 css 文件转换为浏览器能够理解的数据结构后,就需要对其属性的值进行标准化操作了,我们在编写 css 文件的时候,通常可能会写出color: red;font-size: 2emblue、2em这一类属性值浏览器理解不了,所以属性值标准化操作就是把它们转换为浏览器能够理解值。

浏览器基本原理 - 图3

你肯定已经想到了另外一个问题,我们的样式表有很多来源,如果样式表产生冲突了咋办呢?所以浏览器还需要计算每个元素的最终样式,元素的最终样式可以通过开发者工具中的Computed一栏看到。计算规则有继承层叠。继承的意思就是每个子元素都有父元素的样式,层叠是指样式的优先级,当样式产生冲突时就使用这一套规则,层叠的规则大致如下:

  1. 1. 开发者样式 > 读者样式 > 浏览器样式(除非使用!important标记
  2. 2. id选择符 > (伪)类选择符 > 元素选择符
  3. 3. 权重相同时取后面定义的样式

现在我们已经有了 DOM 树和 DOM 树的样式了,但是这两个信息还不能显示出页面,因为我们还没有 DOM 元素的几何位置信息,所以浏览器还要计算元素的几何位置,这个计算过程我们称之为布局阶段。

我们的 DOM 树种可能包含大量不需要显示的结点,比如head标签、设置了display: none的元素,因此在显示之前还需要创建一棵布局树,下图展示了布局树的构造过程。

浏览器基本原理 - 图4

由于页面中有很多的复杂的效果,比如 3D 变换、页面滚动、使用z-index做 z 轴排序等等,所以有了布局树和元素的样式位置信息还是不会绘制页面,为了方便的实现这些效果,渲染引擎还会进行一个分层处理,为特定结点生成专用的图层,并生成一棵图层树。图层可以通过开发者工具的Layers标签直观的观察到。

通常情况下并不是布局树中的每个结点都会包含一个图层,如果一个结点没有对应的图层,那么它就从属于父节点的图层。如果某个元素使用了层叠上下文属性,那么渲染引擎就会将该结点提升为单独的图层;第二种会创建图层的情况是需要裁剪的元素。

我们借助下面的代码来解释裁剪的含义,可以看到代码中把div限制在 200*200 大小矩形中,而下面的文字内容又过于多了, 这么小的区域肯定无法显示完全,这个时候就产生了裁剪。

  1. <style>
  2. div {
  3. width: 200;
  4. height: 200;
  5. overflow:auto;
  6. background: gray;
  7. }
  8. </style>
  9. <body>
  10. <div >
  11. <p> 所以元素有了层叠上下文的属性或者需要被剪裁,那么就会被提升成为单独一层,你可以参看下图:</p>
  12. <p> 从上图我们可以看到,document 层上有 A 和 B 层,而 B 层之上又有两个图层。这些图层组织在一起也是一颗树状结构。</p>
  13. <p> 图层树是基于布局树来创建的,为了找出哪些元素需要在哪些层中,渲染引擎会遍历布局树来创建层树(Update LayerTree)。</p>
  14. </div>
  15. </body>

浏览器基本原理 - 图5