- 本文转载自 https://mp.weixin.qq.com/s/9G...
一、浏览器的渲染原理及流程
- 通常,咱们打包进去的 HTML、CSS、JavaScript 等文件,通过浏览器运行之后就会显示出页面,这个过程就是浏览器的渲染过程来操作实现的。渲染过程的次要工作就是将动态资源转化为可视化界面。
1、渲染流程
- DOM树构建:渲染引擎应用HTML解析器(调用XML解析器)解析HTML文档,将各个HTML元素一一转化成DOM节点,从而生成DOM树;
- CSSOM树构建:CSS解析器解析CSS,并将其转化为CSS对象,将这些CSS对象组装起来,构建CSSOM树;
- 渲染树构建:DOM 树和 CSSOM 树都构建实现当前,浏览器会依据这两棵树构建出一棵渲染树;
- 页面布局:渲染树构建结束之后,元素的地位关系以及须要利用的款式就确定了,这时浏览器会计算出所有元素的大小和相对地位;从而有了盒模型。
- 页面绘制:页面布局实现之后,浏览器会将依据解决进去的后果,把每一个页面图层转换为像素,并对所有的媒体文件进行解码。
2、DOM树的构建
- 为什么要构建DOM树呢? 这是因为,浏览器无奈间接了解和应用 HTML,所以须要将HTML转化为浏览器可能了解的构造——DOM树。
- DOM 树形容的是 HTML 标签的层级关系。在页面中,每个HTML标签都会被浏览器解析成文档对象。HTML实质上就是一个嵌套构造,在解析时会把每个文档对象用一个树形构造组织起来,所有的文档对象都会挂在document上,这种组织形式就是HTML最根底的构造——文档对象模型(DOM),这棵树的每个文档对象就叫做DOM节点。
在渲染引擎外部,HTML 解析器负责将 HTML 字节流转换为 DOM 构造,其转化过程如下:
1)字符流 → 词(token)
- HTML构造首先会通过状态机做词法剖析的,将字符流合成为词(token)。Token分为Tag Token 和文本 Token。
上面来看一个HTML代码是如何被拆分的:
<body> <div> <p>hello world</p> </div></body>
对于这段代码,能够拆成词:
- 能够看到,Tag Token 又分 StartTag 和 EndTag,<body>、<div>、<p> 就是 StartTag ,</body>、</div>、</p> 就是 EndTag,别离对应图中的蓝色和红色块,文本 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栈是如何工作的,有如下HTML构造:
<html> <body> <div>hello juejin</div> <div>hello world</div> </body></html>
开始时,HTML解析器会创立一个根为 document 的空的 DOM 构造,同时将 StartTag document 的Token压入栈中,而后再将解析进去的第一个 StartTag html 压入栈中,并创立一个 html 的DOM节点,增加到 document 上,接下来body和div标签也会和下面的过程一样,进行入栈操作:
随后就会解析到 div标签中的文本Token,渲染引擎会为该 Token 创立一个文本节点,并将该 Token 增加到 DOM 中,它的父节点就是以后 Token 栈顶元素对应的节点:
接下来就是第一个EndTag div,这时 HTML 解析器会判断以后栈顶元素是否是 StartTag div,如果是,则从栈顶弹出 StartTag div,如下图所示:
再之后的过程就和下面相似了,最终的后果如下:
3、CSSOM树的构建
- 实际上。浏览器在构建 DOM 树的同时,如果款式也加载实现了,那么 CSSOM 树也会同步构建。
CSSOM 树和 DOM 树相似,它次要有两个作用:
- 提供给 JavaScript 操作款式的能力;
- 为渲染树的合成提供根底的款式信息。
- CSSOM 树形容的是选择器之间的层级关系。
1)属性值标准化
在将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); }
2)款式计算
款式计算阶段的目标是为了计算出 DOM 节点中每个元素的具体款式,在计算过程中须要恪守 CSS 的继承和层叠两个规定。
<html> <head> <link href="./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树大抵如下:
4、渲染树的构建
- 为什么要构建渲染树呢? DOM树可能蕴含一些不可见的元素,比方head标签,应用display:none;属性的元素等。所以在显示页面之前,还要额定地构建一棵只蕴含可见元素的渲染树。
- 渲染树就是 DOM 树和 CSSOM 树的联合,会失去一个能够晓得每个节点会利用什么款式的数据结构。这个联合的过程就是遍历整个 DOM 树,而后在 CSSOM 树里查问到匹配的款式。
在不同浏览器里,构建渲染树的过程不太一样:
- 在 Chrome 里会在每个节点上应用 attach() 办法,把 CSSOM 树的节点挂在 DOM 树上作为渲染树。
- 在 Firefox 里会独自结构一个新的构造, 用来连贯 DOM 树和 CSSOM 树的映射关系。
- 同一个 DOM 节点可能会匹配到多个 CSSOM 节点,而最终的成果由哪个 CSS 规定来确定,就是款式优先级的问题了。当一个 DOM 元素受到多条款式管制时,款式的优先级程序如下:内联款式 > ID选择器 > 类选择器 > 标签选择器 > 通用选择器 > 继承款式 > 浏览器默认款式。
5、页面布局
- 通过计算渲染树上每个节点的款式,就能得进去每个元素所占空间的大小和地位。当有了所有元素的大小和地位后,就能够在浏览器的页面区域里去绘制元素的边框了。这个过程就是布局。
- 这个过程中,浏览器对渲染树进行遍历,将元素间嵌套关系以盒模型的模式写入文档流。
6、页面绘制
1)构建图层
- 通过布局,每个元素的地位和大小就有了,那上面是不是就该开始绘制页面了?答案是否定的,因为页面上可能有很多简单的场景,比方3D变动、页面滚动、应用z-index进行z轴的排序等。所以,为了实现这些成果,渲染引擎还须要为特定的节点生成专用的图层,并生成一棵对应的图层树。
- 图层会依照肯定程序叠加在一起,就造成了最终的页面。这里,将页面分解成多个图层的操作就成为分层, 最初将这些图层合并到一层的操作就成为合成, 分层和合成通常是一起应用的。Chrome 引入了分层和合成的机制就是为了晋升每帧的渲染效率。
- 通常状况下,并不是渲染树上的每个节点都蕴含一个图层,如果一个节点没有对应的图层,那这个节点就会属于其父节点的图层。
(1)领有层叠上下文属性的元素
- 咱们看到的页面通常是二维的立体,而层叠上下文可能让页面具备三维的概念。这些 HTML 元素依照本身属性的优先级散布在垂直于这个二维立体的 z 轴上。
- 包含浮动、定位、z-index。
(2)须要裁剪的元素
- 如果有一个固定宽高的div盒子,而外面的文字较多超过了盒子的高度,这时就会产生裁剪,浏览器渲染引擎会把裁剪文字内容的一部分用于显示在 div 区域。
- 当呈现裁剪时,浏览器的渲染引擎就会为文字局部独自创立一个图层,如果呈现滚动条,那么滚动条也会被晋升为独自的图层。
2)绘制图层
- 在实现图层树的构建之后,渲染引擎会对图层树中的每个图层进行绘制,上面就来看看渲染引擎是怎么实现图层绘制的。
渲染引擎在绘制图层时,会把一个图层的绘制分成很多绘制指令,而后把这些指令依照程序组成一个待绘制的列表:
- 通常状况下,绘制一个元素须要执行多条绘制指令,因为每个元素的背景、边框等属性都须要独自的指令进行绘制。
- 绘制列表只是用来记录绘制程序和绘制指令的列表,而绘制操作是由渲染引擎中的合成线程来实现的。当图层绘制列表筹备好之后,主线程会把该绘制列表提交给合成线程。所以,在执行合成操作时并不会影响到主线程的执行。
二、扩大
1、重排和重绘
1)重排
- 当咱们的操作引发了 DOM 树中几何尺寸的变动(扭转元素的大小、地位、布局形式等),这时渲染树里有改变的节点和它影响的节点都要从新计算。这个过程就叫做重排,也称为回流。
- 在改变产生时,要从新经验页面渲染的整个流程,所以开销是很大的。
2)重绘
- 当对 DOM 的批改导致了款式的变动(比方批改色彩、背景色)、但未影响其几何属性时,浏览器不需从新计算元素的几何属性、间接为该元素绘制新的款式(会跳过重排环节),这个过程叫做重绘。
- 当咱们批改元素绘制属性时,页面布局阶段不会执行,因为并没有引起几何地位的变换,所以就间接进入了绘制阶段,而后执行之后的一系列子阶段。相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。
2、JS 对 DOM 的影响
- 当解析器解析HTML时,如果遇到了script标签,判断这是脚本,就会暂停 DOM 的解析,因为接下来的 JavaScript 脚本可能会批改以后曾经生成的 DOM 构造。
- JavaScript 线程会阻塞 DOM 的解析,咱们能够通过CDN、压缩脚本等形式来减速 JavaScript 脚本的加载。
如果脚本文件中没有操作DOM的相干代码,就能够将JavaScript脚本设置为异步加载,能够给script标签增加 async 或 defer 属性来实现脚本的异步加载。
<script async type="text/javascript" src='./index.js'></script><script defer type="text/javascript" src='./index.js'></script>