浏览器页面渲染机制
你不知道的浏览器页面渲染机制 - 掘金 (juejin.cn)
「二」浏览器的渲染流程及组成【必会!】 - 简书 (jianshu.com)
- 页面加载过程是怎样的?
- 浏览器渲染过程分哪三部分?
- Rendering Tree 渲染树等同于DOM树吗?
- DOM是如何构建的?
- CSSOM是如何构建的?
- Rendering Tree是如何构建的?
- 浏览器如果渲染过程中遇到JS文件怎么处理?
- async和defer的作用是什么?有什么区别?
- 为什么操作 DOM 慢?
- 回流和重绘是什么?有什么区别?
- 如何减少回流、重绘?
1. 页面加载过程
- 浏览器根据 DNS 服务器得到域名的 IP 地址
- 向这个 IP 的机器发送 HTTP 请求
- 服务器收到、处理并返回 HTTP 请求
- 浏览器得到返回内容
2. 浏览器渲染过程
解析三个东西:
HTML/SVG/XHTML
➡DOM Tree
CSS
➡CSS Rule Tree
Javascript
脚本
解析完成后,浏览器引擎会通过
DOM Tree
和CSS Rule Tree
来构造Rendering Tree
。Rendering Tree
渲染树并不等同于DOM
树,渲染树只会包括需要显示的节点和这些节点的样式信息。CSS
的Rule Tree
主要是为了完成匹配并把CSS Rule
附加上Rendering Tree
上的每个Element
(也就是每个Frame
)。- 然后,计算每个
Frame
的位置,这又叫layout
和reflow
过程。
最后通过调用操作系统
Native GUI
的API绘制。
3. 构建DOM
- 将字符串转换成
Token
,例如:<html>
、<body>
等。 - 生成节点对象(
Node
)并构建DOM:每个Token
被生成后,会立刻消耗这个Token
创建出节点对象。
4. 构建CSSOM
构建CSSOM
的过程与构建DOM
的过程非常相似,当浏览器接收到一段CSS
,浏览器首先要做的是识别出Token
,然后构建节点并生成CSSOM
。
在这一过程中,浏览器会确定下每一个节点的样式到底是什么,并且这一过程其实是很消耗资源的。因为样式你可以自行设置给某个节点,也可以通过继承获得。在这一过程中,浏览器得递归 CSSOM 树,然后确定具体的元素到底是什么样式。
注意:CSS匹配HTML元素是一个相当复杂和有性能问题的事情。所以,DOM树要小,CSS尽量用id和class,千万不要过渡层叠下去。
5. 构建渲染树
当我们生成 DOM 树和 CSSOM 树以后,就需要将这两棵树组合为渲染树。
- 渲染树只会包括需要显示的节点和这些节点的样式信息,如果某个节点是
display: none
的,那么就不会在渲染树中显示。
浏览器如果渲染过程中遇到JS文件怎么处理?
渲染过程中,如果遇到
<script>
就停止渲染,执行 JS 代码。因为浏览器有GUI渲染线程与JS引擎线程,为了防止渲染出现不可预期的结果,这两个线程是互斥的关系。 JavaScript的加载、解析与执行会阻塞DOM的构建,也就是说,在构建DOM时,HTML解析器若遇到了JavaScript,那么它会暂停构建DOM,将控制权移交给JavaScript引擎,等JavaScript引擎运行完毕,浏览器再从中断的地方恢复DOM构建。
JS文件不只是阻塞DOM的构建,它会导致CSSOM也阻塞DOM的构建
原本DOM和CSSOM的构建是互不影响,同时进行,但是一旦引入了JavaScript,CSSOM也开始阻塞DOM的构建,只有CSSOM构建完毕后,DOM再恢复DOM构建。
这是因为JavaScript不只是可以改DOM,它还可以更改样式,也就是它可以更改CSSOM。因为不完整的CSSOM是无法使用的,如果JavaScript想访问CSSOM并更改它,那么在执行JavaScript时,必须要能拿到完整的CSSOM。所以就导致了一个现象,如果浏览器尚未完成CSSOM的下载和构建,而我们却想在此时运行脚本,那么浏览器将延迟脚本执行和DOM构建,直至其完成CSSOM的下载和构建。也就是说,在这种情况下,浏览器会先下载和构建CSSOM,然后再执行JavaScript,最后在继续构建DOM。
6. async和defer的作用是什么?有什么区别?
1)情况1<script src="script.js"></script>
没有 defer
或 async
,浏览器会立即加载并执行指定的脚本,也就是说不等待后续载入的文档元素,读到就加载并执行。
2)情况2<script async src="script.js"></script>
(异步下载)
async
属性表示异步执行引入的 JavaScript
,与 defer
的区别在于,如果已经加载好,就会开始执行——无论此刻是 HTML 解析阶段还是 DOMContentLoaded
触发之后。需要注意的是,这种方式加载的 JavaScript
依然会阻塞 load
事件。换句话说,async-script
可能在 DOMContentLoaded
触发之前或之后执行,但一定在 load 触发之前执行。
3)情况3 <script defer src="script.js"></script>
(延迟执行)
defer
属性表示延迟执行引入的 JavaScript,即这段 JavaScript 加载时 HTML 并未停止解析,这两个过程是并行的。整个 document 解析完毕且 defer-script
也加载完成之后(这两件事情的顺序无关),会执行所有由 defer-script
加载的 JavaScript 代码,然后触发 DOMContentLoaded
事件。
defer 与相比普通 script,有两点区别:
- **载入 JavaScript 文件时不阻塞 HTML 的解析,执行阶段被放到 HTML 标签解析完成之后。 **
- 在加载多个JS脚本的时候,async是无顺序的加载,而defer是有顺序的加载。
7. 为什么操作 DOM 慢
因为 DOM 是属于渲染引擎中的东西,而 JS 又是 JS 引擎中的东西。当我们用 JS 去操作 DOM 时,本质上是 JS 引擎和渲染引擎之间进行了“跨界交流”。这个“跨界交流”的实现并不简单,它依赖了桥接接口作为“桥梁”(如下图)。
8. 回流(Reflow)和重绘(Repaint)
重绘
当我们对 DOM 的修改导致了样式的变化、却并未影响其几何属性(比如修改了颜色或背景色)时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式(跳过了上图所示的回流环节)。
回流
当我们对 DOM 的修改引发了 DOM 几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来。这个过程就是回流(也叫重排)
二者联系
回流必定会发生重绘,重绘不一定会引发回流。回流所需的成本比重绘高的多,改变父节点里的子节点很可能会导致父节点的一系列回流。
常见引起回流属性和方法
- 添加或者删除可见的DOM元素;
- 元素尺寸改变——边距、填充、边框、宽度和高度
- 内容变化,比如用户在input框中输入文字
- 浏览器窗口尺寸改变——resize事件发生时
- 计算 offsetWidth 和 offsetHeight 属性
- 设置 style 属性的值
常见引起重绘属性和方法
如何减少回流、重绘
使用
transform
替代top
使用
visibility
替换display: none
,因为前者只会引起重绘,后者会引发回流(改变了布局)不要把节点的属性值放在一个循环里当成循环里的变量。
jsfor(let i = 0; i < 1000; i++) { // 获取 offsetTop 会导致回流,因为需要去获取正确的值 console.log(document.querySelector('.test').style.offsetTop) }
不要使用
table
布局,可能很小的一个小改动会造成整个table
的重新布局动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用
requestAnimationFrame
CSS 选择符从右往左匹配查找,避免节点层级过多
将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点。比如对于
video
标签来说,浏览器会自动将该节点变为图层。