你应该知道的前端--渲染原理

3,850 阅读6分钟

浏览器渲染原理

我们很容易将浏览器引擎看作是一个黑盒子,就像电视数据一样,黑盒子会指出显示的数据。那么浏览器是怎么把资源/数据转换到我们所看到的网页的呢

1.1 构建对象模型

HTML描述了网页的结构。为了理解HTML,浏览器引擎首先必须将其转换为DOM(文档对象模型)。浏览器引擎中拥有解析器,它用于将HTML中的数据转换为DOM

浏览器逐个构建DOM,只要第一行代码进来,他就开始解析HTML,向树结构添加节点

具体的构建过程:

  1. 转换: 浏览器从磁盘或网络中读取HTML的原始字节,并根据文件的指定编码
  2. 标签化: 浏览器将字符串转换成符合HTML标准的标签
  3. 词法分析: 标签转换成定义其属性和规则的对象
  4. DOM构建: 在HTML中,嵌套定义了不同标签之间的父子关系。在DOM中,对象被链接到捕获这些关系的树数据结构中。每个HTML标记都由一个DOM节点表示。

在网页上的CSS样式被映射到CSSOM(CSS对象模型)。他非常像DOM,但与DOM不同,他不能逐步构建,由于CSS规则可以相互覆盖,因此浏览器引擎必须进行复杂计算才能确定CSS如何应用于DOM中。换个说法只有把CSS全部加载完才能构建CSSOM对象模型,但这样会阻塞页面渲染。

与处理HTML时一样,我们需要将收到的CSS规则转换成浏览器能够理解和处理的东西。因此我们会重复HTML过程,不过是为了CSS而不是HTML

CSS字节转换成字符,接着转换成令牌和节点,最后链接到CSSOM对象模型的树结构内:

1.2 渲染树构建,布局,绘制

在构建完DOM树和CSSOM树,我们需要将将其合并后形成渲染树

构建

为了构建渲染树,浏览器大体上完成了下列工作

  1. 从DOM树的根节点开始遍历每个可见节点,某些节点不可见(例如脚本标记,元标记等),因为他们不会体现在渲染输出中,所以会被忽略。某些节点通过CSS隐藏,因此在渲染树中也会被隐藏,例如display: none
  2. 对于每个可见节点,为其找到适配的CSSOM规则并应用他们
  3. 构建可见节点.连同其内容和计算的样式

注意: visibility: hiddendisplay: none是不一样的。前者隐藏元素,但元素仍占据着布局空间(即将其渲染成一个空的盒子),而后者则将元素从渲染树中完全移除,元素即不可见,也不是布局的组成部分。

布局

有了渲染树后,我们就可以进入布局阶段,到目前为止,我们计算了那些节点应该是可见的以及他们的计算样式,但我们尚未计算他们在设备视口内的确切位置和大小,这就是布局阶段,也称为重排

HTML采用基于流的布局模型.这意味着大多数情况下只要依次遍历就能计算出几何信息。处于流中靠后位置元素通常不会影响靠前位置元素的几何特征,因此布局可以按从左至有,从上至下的顺序遍历文档。

布局是一个递归的过程,他从根节点(html元素),开始,然后递归遍历部分或所有的框架层次结构,为每一个需要计算的节点,计算几何信息

绘制

在绘制阶段,系统会遍历渲染树,将渲染树的内容显示在屏幕上。

动态变化

在发生变化时,会尽可能做出最小的响应。因此,元素的颜色改变后,只会对该元素进行重绘。元素的位置改变后,只会对该元素及其子元素(可能还有同级元素)进行布局和重绘。添加DOM节点后,会对该节点进行布局和重绘。一些重大变化(例如增大html元素的字体)会导致缓存无效,使得整个渲染树都会进行重新布局和重绘

1.3 JS的问题

在浏览器构建DOM时,如果遇到script标签,他必须立即执行,如果脚本是外部脚本,则必须先下载脚本

在过去,为了执行脚本,解析必须暂停。他只会在JavaScript引擎从脚本执行代码后再次启动。

为什么解析必须停止?这是因为js可以改变DOM,js可以通过添加节点来改变DOM结构,也可以使用document.wrtie()添加内容来使HTML其余部分无效

JS也可以查询关于DOM的内容,如果这种情况在DOM仍在构建时发生,他可能会返回意外的结果

1.4 阻塞渲染的CSS

JavaScript阻止解析,因为它可以修改文档,CSS无法修改文档,似乎没有理由阻塞解析,但是,JavaScript还可以读取和修改CSSOM的属性。如果浏览器尚未完成CSSOM的下载和构建,而我们此时想运行脚本,浏览器将延迟JS执行和DOM构建,直至其完成CSSOM的下载和构建

1.5 defer和async

当然我们可以通过设置deferasync属性来异步加载不太重要的脚本

这两个属性都告诉浏览器,他可能会在后台加载脚本时继续解析HTML,然后再在加载后执行脚本,这样脚本下载不会阻止DOM构建和页面呈现。用户可以在所有脚本完成加载之前看到页面

两者的区别就是他们将在那一刻执行脚本,在这之前我们需要了解浏览器为其加载的每个网页追踪细粒度时间戳

  • domLoading: 浏览器即将开始解析第一批收到的HTML文档字节
  • domInteractive: 表示浏览器完成对所有HTML的解析并且DOM构建完成的时间点
  • domContentLoaded: 表示DOM准备就绪并且没有样式表阻止JavaScript执行的时间点
  • domComplete: 所有处理完成,并且网页上的所有资源都已经下载完毕
  • loadEvent: 作为每个网页加载的最后一步,浏览器会触发onload事件,以便触发额外的应用逻辑

defer的执行将在domInteractive完成之后,domContentLoaded之前开始,他保证脚本将按照他们在HTML中出现的顺序执行,并且不会阻塞解析器

async脚本在完成下载之后和窗口load事件之前的某一个时间点执行,这意味着异步脚本可能不按他们在HTML中出现的顺序执行,这意味着他们可能会阻止DOM构建

参考链接:

Building the DOM faster: speculative parsing, async, defer and preload

浏览器工作原理