首先给出几道面试题,以题促学嘛。
经典面试题
(1)简述浏览器渲染原理
(2)为什么操作 DOM 慢?插入几万个 DOM,如何实现页面不卡顿?
(3)什么情况阻塞渲染
(4)在不考虑缓存和优化网络协议的前提下,考虑可以通过哪些方式来最快的渲染页面,也就是常说的关键渲染路径?
开始讲解浏览器渲染原理
我们知道执行 JS 有一个JS引擎,那么执行渲染也有一个渲染引擎。同样,渲染引擎在不同的浏览器中也不是都相同的。比如在 Firefox 中叫做 Gecko,在 Chrome 和 Safari 中都是基于 WebKit 开发的。我们也会主要学习关于 WebKit的这部分渲染引擎内容。
浏览器接收到 HTML 文件并转换为 DOM 树
(1)当打开一个网页时,浏览器首先请求对应的HTML文件。浏览器首先将网络传输中的0/1字节数据转化为字符串,即html/CSS/JS文件。
(2)数据转换为字符串后,浏览器通过词法分析转换为标记(token,还是字符串,是构成代码的最小单位),这个过程即为标记化,即将代码分拆成一块块,并给这些内容打上标记,便于理解这些最小单位的代码是什么意思。
(3)结束标记化后,这些标记会紧接着转换为 Node,最后这些 Node 会根据不同 Node 之前的联系构建为一颗 DOM 树。
将 CSS 文件转换为 CSSOM 树
上一个过程类似,浏览器会确定下每一个节点的样式到底是什么,并且这一过程其实是很消耗资源的。因为样式你可以自行设置给某个节点,也可以通过继承获得。在这一过程中,浏览器得递归 CSSOM 树,然后确定具体的元素到底是什么样式。
<div>
<a> <span></span> </a>
</div>
<style>
span {
color: red;
}
div > a > span {
color: red;
}
</style>
对于第一种设置样式的方式来说,浏览器只需要找到页面中所有的 span 标签然后设置颜色,但是对于第二种设置样式的方式来说,浏览器首先需要找到所有的 span 标签,然后找到 span 标签上的 a 标签,最后再去找到 div 标签,然后给符合这种条件的 span 标签设置颜色,这样的递归过程就很复杂。所以我们应该尽可能的避免写过于具体的 CSS 选择器,然后对于 HTML 来说也尽量少的添加无意义标签,保证层级扁平。
生成渲染树
生成 DOM 树和 CSSOM 树以后,就需要将这两棵树组合为渲染树。在这一过程中,不是简单的将两者合并就行了。渲染树只会包括需要显示的节点和这些节点的样式信息,如果某个节点是 display: none 的,那么就不会在渲染树中显示。当浏览器生成渲染树以后,就会根据渲染树来进行布局(也可以叫做回流),然后调用 GPU 绘制,合成图层,显示在屏幕上。
从上述过程看性能优化
什么情况阻塞渲染
(1)渲染的前提是生成渲染树,所以 HTML 和 CSS 肯定会阻塞渲染。如果你想渲染的越快,你越应该降低一开始需要渲染的文件大小,并且扁平层级,优化选择器。
(2)当浏览器在解析到 script 标签时,会暂停构建 DOM,完成后才会从暂停的地方重新开始更快,就不应该在首屏就加载 JS 文件,这也是都建议将 script 标签放在 body 标签底部的原因。
并不是说 script 标签必须放在底部,因为你可以给 script 标签添加 defer 或者 async 属性。加上 defer 属性以后,表示该 JS 文件会并行下载,但是会放到 HTML 解析完成后顺序执行。对于这种情况你可以把 script 标签放在任意位置。
对于没有任何依赖的 JS 文件可以加上 async 属性,表示 JS 文件下载和解析不会阻塞渲染。
关于重绘与回流
重绘:更改节点外观而不影响布局,比如改变color属性;
回流:布局或几何属性需要改变。
回流必定会发生重绘,重绘不一定会引发回流。回流所需的成本比重绘高的多,改变父节点里的子节点很可能会导致父节点的一系列回流。
可能导致性能问题的几个动作:改变 window 大小;改变字体;添加或删除样式;文字改变;定位或者浮动;盒模型
几种方法减少重绘与回流:
(1)使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局);
(2)不要把节点的属性值放在一个循环里当成循环里的变量;
for(let i = 0; i < 1000; i++) {
// 获取 offsetTop 会导致回流,因为需要去获取正确的值
console.log(document.querySelector('.test').style.offsetTop)
}
(3) 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局
(4) 动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用 requestAnimationFrame
(5) CSS 选择符从右往左匹配查找,避免节点层级过多
(6) 将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点。比如对于 video 标签来说,浏览器会自动将该节点变为图层。
设置节点为图层的方式有很多,我们可以通过以下几个常用属性可以生成新图层
will-change
video、iframe标签