本文内容是极客时间《浏览器工作原理与实践》专栏的读书笔记,均是基于 Chrome 浏览器,默认读者已经有一定的前端基础。
浏览器架构演进
早起浏览器使用的是单进程架构,即所有的工作都运行在同一个进程里面,这样的架构有诸多弊病。比如一些复杂的 js 代码引起渲染模块的崩溃,在任意时刻同时只能有一个模块在运行,另外也存在一些安全性的问题,因为只有一个进程,那么其它插件就能访问到该进程的所有资源。遇到的问题就是不安全、不流畅、不稳定。
为了解决这些问题,浏览器架构逐渐向多进程架构发展,早起浏览器用了浏览器主进程、插件进程、渲染进程共三个进程。因为进程之间是相互隔离的,所以一个插件的崩溃就不会再导致其它页面的崩溃了,同样的道理也不会影响到其它页面的流畅度。
目前的浏览器将网络进程和 GPU 进程从浏览器主进程中分离出来,所以加上渲染进程和插件进程就有 5 个进程了,而插件进程并不是每个页面都一定需要的,所以用 Chrome 打开一个页面时,那至少就会出现四个进程。
当然这里的进程数是不一定的,比如同一个站点的页面或者在页面中使用了 iframe 等等情况都会影响进程数量。这里多说一嘴,虽然浏览器采用了多进程架构,但是同一个站点可能会使用相同的渲染进程,所以有时也会遇到一个页面崩溃,导致其它所有页面一起崩溃的情况。
毫无疑问,多进程架构虽然有诸多优点,但是也会占用很多的资源,同时各个模块之间的耦合度较高,变得更难于适应新需求,为了解决这些问题,Chrome 官方团队在 2016 年使用「面向服务架构」的设计思想设计了新的 Chrome 架构。也就是说 Chrome 整体架构会朝向现代操作系统所采用的「面向服务的架构」来发展,原来的各个模块会被重构成独立的服务,每个服务都可以在独立的进程中运行。
同时 CHrome 还提供了灵活的弹性架构,在强大性能设备上会以多进程的方式运行基础服务,但是在占用资源受限的设备上会将多个服务整合到一个进程中,从而节省内存的占用。
浏览器渲染过程
渲染机制比较复杂,所以渲染过程会被分为很多子阶段,HTML、CSS、JS 经过这些子阶段最终输出位像素,我们把这个流程称之为渲染流水线。按照时间顺序可以把流水线分为构建 DOM 树、样式计算、布局阶段、分层、绘制、分块、光栅化和合成等阶段,每个阶段都有相应的输入、输出内容。
由于浏览器无法直接理解 HTML,所以需要将 HTML 转换为 DOM 树,在开发者工具中我们能看到 DOM 树的结构,可以看到 DOM 树和 HTML 内容基本是一模一样的,但是 DOM 是保存在内存中的树状结构,可以通过 js 查询、修改其中的内容。
DOM 树已经构建出来了,那么下一步就是要为 DOM 节点生成对应的样式了,与 HTML 一样,浏览器也无法直接理解 CSS,所以需要将 CSS 转换为浏览器能理解的样式表(
styleSheets),在控制台输入document.styleSheets
就能看到页面所使用的样式了,这个样式表把通过 link 引入的 css 文件、<style>
标记内的 css 和元素 style 属性内嵌的 css 都包含进去了。
将 css 文件转换为浏览器能够理解的数据结构后,就需要对其属性的值进行标准化操作了,我们在编写 css 文件的时候,通常可能会写出color: red;font-size: 2em
,blue、2em
这一类属性值浏览器理解不了,所以属性值标准化操作就是把它们转换为浏览器能够理解值。
你肯定已经想到了另外一个问题,我们的样式表有很多来源,如果样式表产生冲突了咋办呢?所以浏览器还需要计算每个元素的最终样式,元素的最终样式可以通过开发者工具中的Computed
一栏看到。计算规则有继承和层叠。继承的意思就是每个子元素都有父元素的样式,层叠是指样式的优先级,当样式产生冲突时就使用这一套规则,层叠的规则大致如下:
1. 开发者样式 > 读者样式 > 浏览器样式(除非使用!important标记 )
2. id选择符 > (伪)类选择符 > 元素选择符
3. 权重相同时取后面定义的样式
现在我们已经有了 DOM 树和 DOM 树的样式了,但是这两个信息还不能显示出页面,因为我们还没有 DOM 元素的几何位置信息,所以浏览器还要计算元素的几何位置,这个计算过程我们称之为布局阶段。
我们的 DOM 树种可能包含大量不需要显示的结点,比如head
标签、设置了display: none
的元素,因此在显示之前还需要创建一棵布局树,下图展示了布局树的构造过程。
由于页面中有很多的复杂的效果,比如 3D 变换、页面滚动、使用z-index
做 z 轴排序等等,所以有了布局树和元素的样式位置信息还是不会绘制页面,为了方便的实现这些效果,渲染引擎还会进行一个分层处理,为特定结点生成专用的图层,并生成一棵图层树。图层可以通过开发者工具的Layers
标签直观的观察到。
通常情况下并不是布局树中的每个结点都会包含一个图层,如果一个结点没有对应的图层,那么它就从属于父节点的图层。如果某个元素使用了层叠上下文属性,那么渲染引擎就会将该结点提升为单独的图层;第二种会创建图层的情况是需要裁剪的元素。
我们借助下面的代码来解释裁剪的含义,可以看到代码中把div
限制在 200*200 大小矩形中,而下面的文字内容又过于多了, 这么小的区域肯定无法显示完全,这个时候就产生了裁剪。
<style>
div {
width: 200;
height: 200;
overflow:auto;
background: gray;
}
</style>
<body>
<div >
<p> 所以元素有了层叠上下文的属性或者需要被剪裁,那么就会被提升成为单独一层,你可以参看下图:</p>
<p> 从上图我们可以看到,document 层上有 A 和 B 层,而 B 层之上又有两个图层。这些图层组织在一起也是一颗树状结构。</p>
<p> 图层树是基于布局树来创建的,为了找出哪些元素需要在哪些层中,渲染引擎会遍历布局树来创建层树(Update LayerTree)。</p>
</div>
</body>