ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
[TOC] # 渲染引擎工作流程 如下图所示为渲染引擎的渲染流程示意图,其以HTML/JavaScript/CSS等文件作为输入,以可视化内容作为输出。 ![](https://box.kancloud.cn/99fed7d2c4cfe228565907a8bf00fbd3_3078x615.png) 1. **Parsing HTML to Construct DOM Tree** 渲染引擎使用HTML解析器(调用XML解析器)解析HTML(XML)文档,将各个HTML(XML)元素逐个转化成DOM节点,从而生成DOM树。 同时,渲染引擎使用CSS解析器解析外部CSS文件以及HTML(XML)元素中的样式规则。元素中带有视觉指令的样式规则将用于下一步,以创建另一个树结构:渲染树。 2. **Render Tree construction** 渲染引擎使用第1步CSS解析器解析得到的样式规则,将其附着到DOM树上,从而构成渲染树。 渲染树包含多个带有视觉属性(如颜色和尺寸)的矩形。这些矩形的排列顺序就是它们将在屏幕上显示的顺序。 3. **Layout of Render Tree** 渲染树构建完毕之后,进入本阶段进行“布局”,也就是为每个节点分配一个应出现在屏幕上的确切坐标。 4. **Painting Render Tree** 渲染引擎将遍历渲染树,并调用显示后端将每个节点绘制出来。 <br> <br> # 渲染引擎组成模块 下图所示为渲染引擎工作流程中各个步骤所对应的模块,其中第1步和第2步涉及到多个模块,并且耦合程度较高。这样的设计会为了达到更好的用户体验,渲染引擎尽快将内容显示在屏幕上。它不必等到整个HTML文档解析完毕之后,就可以开始渲染树构建和布局设置。在不断接收和处理来自网络的其余内容的同时,渲染引擎会将部分内容解析并显示出来。 ![](https://box.kancloud.cn/4e1fdf6f2113e841bcf9804c6c20eeaf_3078x1497.png) 从图中可以看出,渲染引擎主要包含(或调用)的模块有: * **HTML(XML)解析器** 解析HTML(XML)文档,主要作用是将HTML(XML)文档转换成DOM树。 * **CSS解析器** 将DOM中的各个元素对象进行计算,获取样式信息,用于渲染树的构建。 * **JavaScript解释器** 使用JavaScript可以修改网页的内容、CSS规则等。JavaScript解释器能够解释JavaScript代码,并通过DOM接口和CSSOM接口来修改网页内容、样式规则,从而改变渲染结果。 * **布局** DOM创建之后,渲染引擎将其中的元素对象与样式规则进行结合,可以得到渲染树。布局则是针对渲染树,计算其各个元素的大小、位置等布局信息。 * **绘图** 使用图形库将布局计算后的渲染树绘制成可视化的图像结果。 <br> <br> # 为什么操作 DOM 慢 因为 DOM 是属于渲染引擎中的东西,而 JS 又是 JS 引擎中的东西。当我们通过 JS 操作 DOM 的时候,其实这个操作涉及到了两个线程之间的通信,那么势必会带来一些性能上的损耗。操作 DOM 次数一多,也就等同于一直在进行线程之间的通信,并且操作 DOM 可能还会带来重绘回流的情况,所以也就导致了性能上的问题。 <br> > 经典面试题:插入几万个 DOM,如何实现页面不卡顿? 对于这道题目来说,首先我们肯定不能一次性把几万个 DOM 全部插入,这样肯定会造成卡顿,所以解决问题的重点应该是如何分批次部分渲染 DOM。大部分人应该可以想到通过`requestAnimationFrame`的方式去循环的插入 DOM,其实还有种方式去解决这个问题:**虚拟滚动**(virtualized scroller)。 **这种技术的原理就是只渲染可视区域内的内容,非可见区域的那就完全不渲染了,当用户在滚动的时候就实时去替换渲染的内容。** ![](https://box.kancloud.cn/68ad7133d494270aaa35629d94075fe4_1204x380.gif) <br> 即使列表很长,但是渲染的 DOM 元素永远只有那么几个,当我们滚动页面的时候就会实时去更新 DOM,这个技术就能顺利解决这道经典面试题。如果你想了解更多的内容可以了解下这个[react-virtualized](https://link.juejin.im/?target=https%3A%2F%2Fgithub.com%2Fbvaughn%2Freact-virtualized)。 <br> # 资源异步同步加载 首先渲染的前提是生成渲染树,所以 HTML 和 CSS 肯定会阻塞渲染。如果你想渲染的越快,你越应该降低一开始需要渲染的文件**大小**,并且**扁平层级,优化选择器**。 <br> 然后当浏览器在解析到`script`标签时,会暂停构建 DOM,完成后才会从暂停的地方重新开始。也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都建议将`script`标签放在`body`标签底部的原因。 <br> 当然在当下,并不是说`script`标签必须放在底部,因为你可以给`script`标签添加`defer`或者`async`属性。 <br> ## 解析过程 ![](https://box.kancloud.cn/be80a7a35e1e8654494345fe3dc2b05b_452x397.png) ## 普通script 文档解析的过程中,如果遇到`script`脚本,就会停止页面的解析进行下载(但是Chrome会做一个优化,如果遇到`script`脚本,会快速的查看后边有没有需要下载其他资源的,如果有的话,会先下载那些资源,然后再进行下载`script`所对应的资源,这样能够节省一部分下载的时间)。 资源的下载是在解析过程中进行的,虽说`script1`脚本会很快的加载完毕,但是他前边的`script2`并没有加载&执行,所以他只能处于一个挂起的状态,等待`script2`执行完毕后再执行。 当这两个脚本都执行完毕后,才会继续解析页面。 ![](https://box.kancloud.cn/2dd49b9b7f85a20e441652fb78388a09_1640x326.png) <br> ## defer * 如果`script`标签设置了`defer`性,则浏览器会异步的下载该文件并且不会影响到后续`DOM`的渲染; * 如果有多个设置了`defer`的`script`标签存在,则会按照顺序执行所有的`script`; * `defer`脚本会在文档渲染完毕后,`DOMContentLoaded`事件调用前执行。 * 文档解析时,遇到设置了`defer`的脚本,就会在后台进行下载,但是并不会阻止文档的渲染,当页面解析&渲染完毕后。 * 会等到所有的`defer`脚本加载完毕并按照顺序执行,执行完毕后会触发`DOMContentLoaded`事件。 ![](https://box.kancloud.cn/96e451a0d1bd7228eaa4160303f77d6d_1320x282.png) <br> 使用场景 如果你的脚本代码依赖于页面中的`DOM`元素(文档是否解析完毕),或者被其他脚本文件依赖。 **例:** 1. 评论框 2. 代码语法高亮 3. `polyfill.js` <br> ## async * `async`的设置,会使得`script`脚本异步的加载并在允许的情况下执行 * `async`的执行,并不会按着`script`在页面中的顺序来执行,而是谁先加载完谁执行。 * `async`脚本会在加载完毕后执行。 * `async`脚本的加载不计入`DOMContentLoaded`事件统计,也就是说下图两种情况都是有可能发生的 ![](https://box.kancloud.cn/85943b897cd9bca91f42f23daeee8637_1310x264.png) ![](https://box.kancloud.cn/c1033591cd8f38cb1d467a4752537b93_880x168.png) <br> 使用场景 如果你的脚本并不关心页面中的`DOM`元素(文档是否解析完毕),并且也不会产生其他脚本需要的数据。 **例:** 1. 百度统计 > 如果不太能确定的话,用`defer`总是会比`async`稳定。 <br> # 参考资料 [现代浏览器工作原理(一)](http://chuquan.me/2018/01/21/browser-architecture-overview/) 前端面试之道 - 掘金手册 [浅谈script标签中的async和defer](https://www.cnblogs.com/jiasm/p/7683930.html)