渲染引擎用来解析 HTML 和 CSS,渲染网页。
渲染引擎用来渲染页面,JS 引擎用来解析和执行 JS。
由于 JS 是单线程的,因此浏览器的 JS 引擎和渲染引擎是互斥的,当其中一个执行时,另一个只能挂起等待。
常见的浏览器内核:
浏览器内核本来是分成渲染引擎和 JS 引擎两部分,但是由于 JS 引擎越来越独立,浏览器内核就倾向于只指渲染引擎了。正是因为浏览器内核不同,浏览器才有兼容问题。
- Chrome 浏览器以前是 Webkit 内核,现在是 Blink 内核。
- Edge 浏览器以前是 Webkit 内核,现在是 Blink 内核。
Edge 浏览器是微软研发的,在 Windows10 中取代 IE 浏览器成为默认浏览器。
IE 浏览器是 Trident 内核,在 2023 年 2 月 14 日永久停用。 - Opera 浏览器最初是自己的 Presto 内核,后来是 Webkit,现在是 Blink 内核。
- Safari 浏览器是 Webkit 内核。
- Firefox 浏览器是 Gecko 内核。
UC 浏览器、百度浏览器是 Trident 内核。
360 浏览器、搜狗浏览器是 Trident + Webkit 双内核。和钱相关的会自动选择 Trident 内核,安全性高一些;其余时候会只能选择 Webkit 运行。
浏览器的渲染引擎解析一个网页的过程:
-
HTML --> HTML Parser --> DOM Tree
:渲染引擎对 HTML 从上到下进行解析,生成 DOM 树。
-
Style Sheets --> CSS Parser --> Style Rules
:在解析 HTML 的过程中,如果发现有 CSS,对其进行解析,生成 CSSOM 规则树。CSS 的下载和解析不会阻塞 HTML 的继续解析。
-
Attachment:将解析完成的 CSSOM 规则树应用到 DOM 树上生成渲染树。在渲染树上有记录每个节点的样式信息,但是没有记录大小信息和位置信息。
只有 DOM 树和 CSS 规则树都解析完成,才能生成渲染树。
也就是说, CSS 文件的下载和解析不会影响 DOM 树的生成,但是会影响渲染树的生成。DOM 树和渲染树可能不是一一对应的。比如:某个元素的 CSS 属性是
display:none
,默认不需要渲染出来。这个元素在 DOM 树上是存在的,在渲染树上是不存在的。为了达到更好的用户体验,浏览器的渲染引擎会尽快将已经构建好的一部分内容显示在屏幕上,而不必等到整个 HTML 文档都解析完毕之后才开始构建渲染树树和布局进行绘制。
- Layout:计算布局样式,得出渲染树上的每个节点的大小信息和位置信息,生成绝对的坐标。
- Painting:将渲染树中的每个节点转为实际的像素点绘制到屏幕上,显示出来。
浏览器的回流与重绘:
回流一定会引起重绘,非常消耗性能;重绘不一定会引起回流,性能消耗相比回流来说要小。
回流 reflow(重布局、重排):
第一次计算节点的大小和位置,称为布局;之后修改了节点的大小和位置重新计算,称为回流。
由于浏览器的流布局,对渲染树的计算通常只需要遍历一次就可以完成。但 table 及其内部元素除外,它可能需要多次计算才能确定好其在渲染树中节点的属性,这就是应该尽量避免使用 table 布局页面的原因之一。
引发回流的操作:
- DOM 树结构发生变化:添加节点、删除节点。
- 改变了布局:修改元素的尺寸、位置信息。
- 浏览器窗口 resize。
- 获取元素的一些属性时:offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight、
getComputedStyle()
等。当获取这些全局属性时,此时页面上的其他元素的布局和样式需要处于最新状态,这会引起多次的回流和重绘,称为强制回流。
回流优化的方法:
- 尽量避免频繁地 DOM 操作:可以在一个 DocumentFragment 或者父元素中将要执行的 DOM 操作都执行完,再一次性地添加到 DOM 上。
- 修改元素的大小、位置时尽量一次性修改。
例如:要将 box 的宽改为
100px
,高改为200px
,最好不要使用box.style.width = '100px'; box.style.height = '200px';
这种方式一个一个修改,可以通过动态添加 class 的方式一次性修改。 - 脱离文档流的元素的变化不会影响到其他元素,可以使需要多次重排的元素脱离文档流来减少对其他元素的影响。
- 由于属性为
display: none
的元素不在渲染树中,对隐藏的元素操作不会引发其他元素的重排。所以,如果要对一个元素进行复杂的操作时,可以先隐藏它,操作完成后再显示,这样只在隐藏和显示时触发两次重排。
重绘 repaint:
第一次将渲染树中的每个节点转为实际的像素点绘制到屏幕上,称为绘制;之后重新渲染,称为重绘。
引发重绘的操作:修改元素的外观,例如文字颜色、背景颜色、边框样式等。
浏览器的 Composite 合成:
浏览器在 Painting 绘制的过程中,还可以进行 Composite 合成,这是浏览器提供的一种优化手段。
浏览器可以将布局后的元素绘制到多个图层中。video
、canvas
、iframe
元素,设置了 position: fiexed
、3D transforms
、animation、trasnition 时设置 opacity、transform
、will-change
(一个实验性的属性。提前告诉浏览器元素可能发生哪些变化) 属性的元素,都会被绘制到一个新的合成层中。而其他的元素都会被绘制在同一个图层中。
合成层可以提升性能,因为每个合成图层都是单独渲染的;并且会交给 GPU 来处理,比 CPU 更快;而且当需要重绘时,合成层只需要重绘本身,不会影响到其他的图层。
可以在 chrome 开发者工具中查看图层。
<style>
.box {
width: 100px;
height: 100px;
}
.box1 {
background: red;
}
.box2 {
background: green;
position: fixed;
}
.box3 {
background: blue;
position: fixed;
}
</style>
<div class="box box1"></div>
<div class="box box2"></div>
<div class="box box3"></div>
可以看到有三个图层,其中两个是合成层。
对于有动画效果的元素,可以提升为合成层,来减少重绘对其他元素的影响,提升性能。虽然合层成确实可以在某种程度上提高性能,但是是以内存管理为代价的,不应作为 Web 性能优化策略的一种方式来过度使用。
<script>
元素和页面解析的关系:
默认情况下,在遇到 <script>
元素时,JS 的下载和执行会阻塞 HTML 的解析,只有下载好并执行完脚本才会继续解析 HTML。因为 JS 的作用之一就是操作 DOM,如果等到 DOM 树构建完成并渲染之后再执行 JS,JS 修改到 DOM 的话会造成严重的回流和重绘,影响页面的性能。
JS 的下载和解析会阻塞 HTML 的继续解析。
CSS 的下载和解析不会阻塞 HTML 的继续解析。
为了解决这个问题,除了可以将 <script>
元素放到 <body>
的最底部外,<script>
元素还提供了两个属性:defer 和 async。
- defer:只适用于外部脚本。JS 脚本会立即在新的线程中单独下载,不会阻塞 HTML 的解析;但是脚本下载完成后,不会立即执行,会等待 HTML 解析完成之后,DOMContentLoaded 事件发出之前再执行。多个脚本会按照在 HTML 文档中的顺序依次执行。
- async:只适用于外部脚本。JS 脚本会立即在新的线程中单独下载,不会阻塞 HTML 的解析;但是脚本下载完成后,会立即执行,此时会阻塞 HTML 的解析。多个脚本的执行顺序无法确定。
在设置了 defer 属性的 JS 文件中,访问到 DOM 元素一定是存在的;在设置了 async 属性的 JS 文件中,访问到 DOM 元素不一定存在。