画了20张图,详解浏览器渲染引擎工作原理

农村四月闲人少,勤学苦攻把名扬。这篇文章主要讲述画了20张图,详解浏览器渲染引擎工作原理相关的知识,希望能为你提供帮助。
今天我们来学习一下浏览器渲染引擎的工作原理,文章内容较多,建议先收藏再学习!
先来看看Chrome浏览器的架构图:

画了20张图,详解浏览器渲染引擎工作原理

文章图片

通常,我们编写的html、CSS、javascript等文件,经过浏览器运行之后就会显示出页面,那他们是如何转化为页面的?这背后的原理是什么?这个过程就是浏览器的渲染进程来操作实现的。浏览器的渲染进程的主要任务就是将静态资源转化为可视化界面: 
画了20张图,详解浏览器渲染引擎工作原理

文章图片

对于中间的浏览器,它就是一个黑盒,下面就来看看这个黑盒是如何将静态资源转化为前端界面的。由于渲染机制比较复杂,所以渲染模块在执行过程中会被划分为很多子阶段,输入的静态资源经过这些子阶段,最后输出页面。我们将一个处理流程称为渲染流水线,其大致流程如下图所示: 
画了20张图,详解浏览器渲染引擎工作原理

文章图片

这里主要包含五个过程:
  • DOM树构建:渲染引擎使用HTML解析器(调用XML解析器)解析HTML文档,将各个HTML元素逐个转化成DOM节点,从而生成DOM树;
  • CSSOM树构建:CSS解析器解析CSS,并将其转化为CSS对象,将这些CSS对象组装起来,构建CSSOM树;
  • 渲染树构建:DOM 树和 CSSOM 树都构建完成以后,浏览器会根据这两棵树构建出一棵渲染树;
  • 页面布局:渲染树构建完毕之后,元素的位置关系以及需要应用的样式就确定了,这时浏览器会计算出所有元素的大小和绝对位置;
  • 页面绘制:页面布局完成之后,浏览器会将根据处理出来的结果,把每一个页面图层转换为像素,并对所有的媒体文件进行解码。
对于这五个流程,每一阶段都有对应的产物,分别是:DOM树、CSSOM树、渲染树、盒模型、界面。
下图为渲染引擎工作流程中各个步骤所对应的模块:
画了20张图,详解浏览器渲染引擎工作原理

文章图片

从图中可以看出,渲染引擎主要包含的模块有:
  • HTML解析器:解析HTML文档,主要作用是将HTML文档转换成DOM树;
  • CSS解析器:将DOM中的各个元素对象进行计算,获取样式信息,用于渲染树的构建;
  • JavaScript解释器:使用javaScript可以修改网页的内容、CSS规则等。JavaScript解释器能够解释JavaScript代码,并通过DOM接口和CSSOM接口来修改网页内容、样式规则,从而改变渲染结果;
  • 页面布局:DOM创建之后,渲染引擎将其中的元素对象与样式规则进行结合,可以得到渲染树。布局则是针对渲染树,计算其各个元素的大小、位置等布局信息。
  • 页面绘制:使用图形库将布局计算后的渲染树绘制成可视化的图像结果。
下面就分别来看看这些过程都做了哪些操作。

一、DOM树构建
在说构建DOM树之前,我们首先需要知道,为什么要构建DOM树呢?  这是因为,浏览器是无法直接理解和使用HTML的,所以需要将HTML转化为浏览器能够理解的结构——DOM树。
了解过数据结构的小伙伴对于树结构应该不陌生,树是由结点或顶点和边组成的且不存在着任何环的一种数据结构。一棵非空的树包括一个根结点,还有多个附加结点,所有结点构成一个多级分层结构。下面通过一张图来看看什么是树结构:
画了20张图,详解浏览器渲染引擎工作原理

文章图片

对于上面的三个结构,前两个都是树,他们都只有唯一的根节点,而且不存在环结构。而第三个存在环,所以就不是一个树结构。
说完树结构,就回归正题,来看看什么是DOM树。在页面中,每个HTML标签都会被浏览器解析成文档对象。HTML本质上就是一个嵌套结构,在解析时会把每个文档对象用一个树形结构组织起来,所有的文档对象都会挂在document上,这种组织方式就是HTML最基础的结构——文档对象模型(DOM),这棵树的每个文档对象就叫做DOM节点。 ?
在渲染引擎中,DOM 有三个层面的作用:
  • 从页面的视角来看,DOM 是生成页面的基础数据结构;
  • 从 JavaScript 脚本视角来看,DOM 提供给 JavaScript 脚本操作的接口,通过这套接口,JavaScript 可以对 DOM 结构进行访问,从而改变文档的结构、样式和内容;
  • 从安全视角来看,DOM 是一道安全防护线,一些不安全的内容在 DOM 解析阶段会被拒之门外。
在渲染引擎内部,HTML 解析器负责将 HTML 字节流转换为 DOM 结构,其转化过程如下: 
画了20张图,详解浏览器渲染引擎工作原理

文章图片


1. 字符流→词(token)HTML结构会首先通过分词器将字节流拆分为词(token)。Token分为Tag Token 和文本 Token。下面来看一个HTML代码是如何被拆分的:
< body>
< div>
< p> hello world< /p>
< /div>
< /body>
复制代码

对于这句代码,可以拆成词:
画了20张图,详解浏览器渲染引擎工作原理

文章图片

可以看到,Tag Token 又分 StartTag 和 EndTag,??< body> ??、??< div> ??、??< p> ??就是 StartTag ,??< /body> ??、??< /div> ??、??< /p> ??就是 EndTag,分别对应图中的蓝色和红色块,文本 Token 对应绿色块。 ?
这里会通过状态机将字符拆分成token,所谓的状态机就是将每个词的特征逐个拆分成独立的状态,然后再将所有词的特征字符合并起来,形成一个连通的图结构。那为什么要使用状态机呢?因为每读取一个字符,都要做一次决策,这些决策都和当前的状态有关。 ?
实际上,状态机的作用就是用来做词法分析的,将字符流分解为词(token)。

2. 词(token)→DOM树接下来就需要将 Token 解析为 DOM 节点,并将 DOM 节点添加到 DOM 树中。这个过程是通过栈结构来实现的,这个栈主要用来计算节点之间的父子关系,上面步骤中生成的token会按顺序压入栈中,该过程的规则如下:
  • 如果分词器解析出来是StartTag Token,HTML 解析器会为该 Token 创建一个 DOM 节点,然后将该节点加入到 DOM 树中,它的父节点就是栈中相邻的那个元素生成的节点;
  • 如果分词器解析出来是  文本  Token,那么会生成一个文本节点,然后将该节点加入到 DOM 树中,文本 Token 是不需要压入到栈中,它的父节点就是当前栈顶 Token 所对应的 DOM 节点;
  • 如果分词器解析出来的是EndTag Token,比如是 EndTag div,HTML 解析器会查看 Token 栈顶的元素是否是 StarTag div,如果是,就将 StartTag div从栈中弹出,表示该 div 元素解析完成。
通过分词器产生的新 Token 就这样不停地入栈和出栈,整个解析过程就这样一直持续下去,直到分词器将所有字节流分词完成。
下面来看看这的Token栈是如何工作的,有如下HTML结构:
< html>
< body>
< div> hello juejin< /div>
< p> hello world< /p>
< /body>
< /html>
复制代码

开始时,HTML解析器会创建一个根为 document 的空的 DOM 结构,同时将 StartTag document 的Token压入栈中,然后再将解析出来的第一个 StartTag html 压入栈中,并创建一个 html 的DOM节点,添加到document上,这时Token栈和DOM树如下:
画了20张图,详解浏览器渲染引擎工作原理

文章图片

接下来body和div标签也会和上面的过程一样,进行入栈操作:
画了20张图,详解浏览器渲染引擎工作原理

文章图片

随后就会解析到 div标签中的文本Token,渲染引擎会为该 Token 创建一个文本节点,并将该 Token 添加到 DOM 中,它的父节点就是当前 Token 栈顶元素对应的节点:
画了20张图,详解浏览器渲染引擎工作原理

文章图片

接下来就是第一个EndTag div,这时 HTML 解析器会判断当前栈顶元素是否是 StartTag div,如果是,则从栈顶弹出 StartTag div,如下图所示:
画了20张图,详解浏览器渲染引擎工作原理

文章图片

再之后的过程就和上面类似了,最终的结果如下:
画了20张图,详解浏览器渲染引擎工作原理

文章图片


二、CSSOM树构建
上面已经基本了解了DOM的构建过程,但是这个DOM结构只包含节点,并不包含任何的样式信息。下面就来看看,浏览器是如何把CSS样式应用到DOM节点上的。
同样,浏览器也是无法直接理解CSS代码的,需要将其浏览器可以理解的CSSOM树。实际上。浏览器在构建 DOM 树的同时,如果样式也加载完成了,那么 CSSOM 树也会同步构建。CSSOM 树和 DOM 树类似,它主要有两个作用:
  • 提供给 JavaScript 操作样式的能力;
  • 为渲染树的合成提供基础的样式信息。
不过,CSSOM 树和 DOM 树是独立的两个数据结构,它们并没有一一对应关系。DOM 树描述的是 HTML 标签的层级关系,CSSOM 树描述的是选择器之间的层级关系。可以在浏览器的控制台,通过??document.styleSheets??命令来查看CSSOM树: 
画了20张图,详解浏览器渲染引擎工作原理

文章图片

那CSS样式的来源有哪些呢?
画了20张图,详解浏览器渲染引擎工作原理

文章图片

可以看到,CSS样式的来源主要有三种:
  • 通过 link 引用的外部 CSS 样式文件;
  • ??< style> ??标签内的CSS样式;
  • 元素的style属性内嵌的CSS。
在将CSS转化为树形对象之前,还需要将样式表中的属性值进行标准化处理,比如,当遇到以下CSS样式:
body { font-size: 2em }
p {color:blue; }
div {font-weight: bold}
div p {color:green; }
div {color:red; }
复制代码

可以看到上面CSS中有很多属性值,比如2em、blue、red、bold等,这些数值并不能被浏览器直接理解。所以,需要将所有值转化为浏览器渲染引擎容易理解的、标准化的计算值,这个过程就是属性值标准化。经过标准化的过程,上面的代码会变成这样:
body { font-size: 32px }
p {color: rgb(0, 0, 255); }
div {font-weight: 700}
div p {color: (0, 128, 0); }
div {color: (255, 0, 0); }
复制代码

可以看到,2em被解析成了32px,blue被解析成了rgb(255, 0, 0),bold被解析成700。现在样式的属性已被标准化了,接下来就需要计算 DOM 树中每个节点的样式属性了,这就涉及到 CSS 的继承规则和层叠规则。
(1)样式继承
在 CSS 中存在样式的继承机制,CSS 继承就是每个 DOM 节点都包含有父节点的样式。比如在 HTML 上设置“font-size:20px; ”,那么页面里基本所有的标签都可以继承到这个属性了。
在CSS中,有继承性的属性主要有以下几种:
  1. 字体系列属性
  • font-family:字体系列
  • font-weight:字体的粗细
  • font-size:字体的大小
  • font-style:字体的风格
  1. 文本系列属性
  • text-indent:文本缩进
  • text-align:文本水平对齐
  • line-height:行高
  • word-spacing:单词之间的间距
  • letter-spacing:中文或者字母之间的间距
  • text-transform:控制文本大小写(就是uppercase、lowercase、capitalize这三个)
  • color:文本颜色
  1. 元素可见性
  • visibility:控制元素显示隐藏
  1. 列表布局属性
  • list-style:列表风格,包括list-style-type、list-style-image等
  1. 光标属性
  • cursor:光标显示为何种形态
(2)样式层叠
样式计算过程中的第二个规则是样式层叠。层叠是 CSS 的一个基本特征,它是一个定义了 如何合并来自多个源的属性值的算法。它在 CSS 处于核心地位,CSS 的全称“层叠样式表”正是强调了这一点。这里不再多说。
总之,样式计算阶段的目的是为了计算出 DOM 节点中每个元素的具体样式,在计算过程 中需要遵守 CSS 的继承和层叠两个规则。这个阶段最终输出的内容是每个 DOM 节点的样 式,并被保存在 ComputedStyle 的结构内。
对于以下代码:
< html>
< head>
< link href="https://www.songbingjia.com/android/style.css">
< style>
.juejin {
width: 100px;
height: 50px;
background: red;
}

.content {
font-size: 25px;
line-height: 25px;
margin: 10px;
}
< /style>
< /head>
< body>
< div class="juejin">
< div> CUGGZ< /div>
< /div>
< p style="color: blue" class="content">
< span> hello world< /span>
< p style="display: none; "> 浏览器< /p>
< /p>
< /body>
< /html>
复制代码

最终生成的CSSOM树大致如下:
画了20张图,详解浏览器渲染引擎工作原理

文章图片


三、渲染树构建
在 DOM 树和 CSSOM 树都渲染完成之后,就会进入渲染树的构建阶段。渲染树就是 DOM 树和 CSSOM 树的结合,会得到一个可以知道每个节点会应用什么样式的数据结构。这个结合的过程就是遍历整个 DOM 树,然后在 CSSOM 树里查询到匹配的样式。
在不同浏览器里,构建渲染树的过程不太一样:
  • 在 Chrome 里会在每个节点上使用 attach() 方法,把 CSSOM 树的节点挂在 DOM 树上作为渲染树。
  • 在 Firefox 里会单独构造一个新的结构, 用来连接 DOM 树和 CSSOM 树的映射关系。
那为什么要构建渲染树呢?在上面的示例中可以看到,DOM树可能包含一些不可见的元素,比如head标签,使用display:none; 属性的元素等。所以在显示页面之前,还要额外地构建一棵只包含可见元素的渲染树。
下面来看看构建渲染树的过程: 
画了20张图,详解浏览器渲染引擎工作原理

文章图片

可以看到,DOM树中不可见的节点都没有包含到渲染树中。为了构建渲染树,浏览器上大致做了如下工作:遍历DOM树中所有可见节点,并把这些节点加到布局中,而不可见的节点会被布局树忽略掉,如 head 标签下面的全部内容,再比如 p.p 这个元素,因为它的属性包含 dispaly:none,所以这个元素也没有被包含进渲染树中。如果给元素设置了visibility: hidden; 属性,那这个元素会出现在渲染树中,因为具有这个样式的元素是需要占位的,只不过不需要显示出来。 ?
这里在查找的过程中,出于效率的考虑,会从 CSSOM 树的叶子节点开始查找,对应在 CSS 选择器上也就是从选择器的最右侧向左查找。所以,不建议使用标签选择器和通配符选择器来定义元素样式。 ?
除此之外,同一个 DOM 节点可能会匹配到多个 CSSOM 节点,而最终的效果由哪个 CSS 规则来确定,就是样式优先级的问题了。当一个 DOM 元素受到多条样式控制时,样式的优先级顺序如下:内联样式 > ID选择器 > 类选择器 > 标签选择器 > 通用选择器 > 继承样式 > 浏览器默认样式  ?
CSS常见选择器的优先级如下:
选择器
【画了20张图,详解浏览器渲染引擎工作原理】格式

    推荐阅读