一、前言
浏览器的内核是指支持浏览器运行的最核心的程序,分为两个部分,一是“渲染引擎”,二是“JS引擎”。
渲染引擎在不同的浏览器也不是都相同的,比如:
Firefox(火狐浏览器)中使用 Gecko; Chrome和safari中使用 WebKit
本文主要介绍关于WebKit的渲染引擎内容和相关问题。
本文图解:

二、浏览器工作大体流程
主要分为三个部分:
- 浏览器解析
资源文件解析:
1. HTML/SVG/XHTML,事实上,Webkit有三个C++的类对应这三类文档。解析这三种文件会产生一个DOM Tree
2. CSS,解析CSS会产生CSS规则树
3. Javascript,脚本,主要是通过DOM API和CSSOM API来操作DOM Tree和CSS Rule Tree
- 解析完成后,浏览器引擎会通过DOM Tree 和 CSS Rule Tree来构造Rendering Tree
1. Rendering Tree 渲染树并不等同于DOM树,因为一些像Header或display:none的东西就没必要放在渲染树中了
2. CSS 的 Rule Tree主要是为了完成匹配并把CSS Rule附加上Rendering Tree上的每个Element。也就是DOM结点。也就是所谓的Frame
3. 然后计算每个Frame(也就是每个Element)的位置,这又叫layout和reflow过程
- 调用操作系统Native GUI的API绘制
三、构建DOM
浏览器会遵守一套步骤将HTML文件转换为DOM树,大概可以分为几个步骤

- 浏览器从磁盘或网络读取HTML的原始字节,并根据文件的指定编码(比如UTF-8)将它们转换成字符串。
网络中传输的内容其实都是0和1这些字节数据,当浏览器收到这些字节数据后,会将字节数据变成字符串,也就是代码
- 将字符串转换成Token,例如:
<html>、<body>等, Token中会标识出当前Token是“开始标签”或是“结束标签”亦或是“文本”等信息。
那么节点与节点之间的关系如何维护?
事实上,这就是Token要标识“起始标签” 和“结束标签”等标识的作用
例如: "title" Token的起始标签和结束标签之间的节点肯定都是"head"的子节点。

在上图中描述了节点之间的关系,例如: “Hello”Token位于“title”开始标签与“title”结束标签之间,表明“Hello”Token是“title”Token的子节点。同理“title”Token是“head”Token的子节点
- 生成节点对象并构建DOM
事实上,构建DOM的过程中,不是等所有token都转换完成后再去生成节点对象,而是一边生成token 一边消耗token来生成节点对象。
也就是说,每个Token被生成之后,,会立刻消耗这个Token创建出节点对象。需要注意的是,有结束标签标识的Token不会创建节点对象。
例如:这段HTML文本
<html>
<head>
<title>Web page parsing</title>
</head>
<body>
<div>
<h1>Web page parsing</h1>
<p>This is an example Web page.</p>
</div>
</body>
</html>
上述文本解析的时候,会如下图示:

三、构建CSSOM
DOM会捕获页面的内容,但浏览器需要知道页面该如何展示,所以就需要构建CSSOM。
构建CSSOM的过程和构建DOM的过程非常相似,当浏览器收到一段css,浏览器需要做的是识别出token,然后构建节点并生成CSSOM。
在这个过程中,浏览器会确定下每一个节点的样式是什么,并且这一过程其实很消耗资源的,因为样式你可以自行设置给某个节点,也可以通过继承获得。
在这一过程中,浏览器得递归CSSOM树,然后确定具体的元素到底是什么样式。
注意:
CSS匹配HTML元素是一个相当复杂和有性能问题的事情,所以,DOM树要小,CSS尽量用id和class,千万不要过渡层叠下去。
四、构建“树”
当我们生成DOM树和CSSOM树以后,就需要将这两棵树组合为“渲染树”

在这个一个过程中,不是简单的"树" 合并就可以了。渲染树只会包括需要显示的节点和这些节点的样式信息 ,如果某个节点是 display: none的,那么就不会在渲染树中显示
五、布局与绘制
当浏览器生成“渲染树”以后,就会根据渲染树来进行布局(也称作回流)。在这个阶段浏览器要做的事情就是要弄清楚各个节点在页面中的确切位置和大小。 通常这一行为也称为“自动重排”。
布局流程的输出是一个“盒模型”,它会精确地捕获每个元素在视口内的确切位置和尺寸,所有相对测量值都将转换为屏幕上的绝对像素。
布局完成后,浏览器会立即发出“Paint Setup” 和 “Paint” 事件,将渲染树转换成屏幕上的像素。
六、问题讨论
问题一、渲染过程中遇到JS文件怎么处理?
JavaScript的加载、解析与执行会阻塞DOM的构建,也就是说, 在构建DOM时,HTML解析器若遇到了JavaScript,那么它会暂停构建DOM,将控制权移交给JavaScript引擎,等JavaScript引擎运行完毕,浏览器再从中断的地方恢复DOM构建。
也就是说,如果想首屏页面渲染的越快,就越不该在首屏加载js文件,这也是建议将 'script' 标签放在 'body' 底部的原因。
当然在当下,并不是说script标签必须放在底部,可以给script 标签添加 defer或 async 属性。
JS文件不只是阻塞DOM的构建,它会导致CSSOM也阻塞DOM的构建。
原本DOM和CSSOM的构建是互不影响的,但是一旦引入了JavaScript,CSSOM也开始阻塞DOM的构建,只有CSSOM构建完毕后,DOM再恢复DOM构建。
为何引入JavaScript就会发生这种情况?
主要是因为JavaScript不只是可以改变DOM,它还可以更改样式,也就是它可以更改CSSOM。
而不完整的CSSOM是无法使用的,但JavaScript中想访问CSSOM并更改它,那么在执行JavaScript时,必须能拿到完整的CSSOM。
所以就导致了一个现象,如果浏览器尚未完成CSSOM的构建和下载,但此时又想运行脚本,那么浏览器将延迟脚本执行和DOM构建,直到其完成CSSOM的下载和构建
也就是说,浏览器会先下载和构建CSSOM,然后再执行JavaScript ,最后再继续构建DOM.
问题二、你真的了解回流和重绘吗?

当网页生成的时候,至少会渲染一次。在用户访问的过程中,还会不断重新渲染,重新渲染会重复上图中的第四部(回流)+ 第五步(重绘)或者只有第五步(重绘)。
1. 重绘:当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观、风格,而不会影响布局的,比如background-color
2. 回流:当render tree中的一部分(或全部)因为元素的规模尺寸、布局、隐藏等改变而需要重新构建
回流必定会发生重绘,重绘不一定会引发回流