JavaScript中 js 引擎和渲染引擎(浏览器内核)是独立实现的。使用 js 去操作 DOM 时,本质上是 JS 引擎和渲染引擎之间进行了“跨界交流”。每操作一次 DOM,都要跨界一次。跨界的次数一多,就会产生比较明显的性能问题。
DOM操作影响性能的原因主要有以下两点:
- 在浏览器中,DOM的实现和ECMAScript的实现是分离的。比如在Chrome中使用WebKit中的WebCore处理DOM和渲染,但ECMAScript是在V8引擎中实现的。所以通过JavaScript代码调用DOM接口,相当于两个独立模块的交互。相比较在同一模块中的调用,这种跨模块的调用其性能损耗是很高的。
- DOM操作通常会导致浏览器的重绘(repaint)和回流(reflow,也叫重布局),重绘和回流的代价很高。
机制:
- 从下载文档到渲染页面的过程中,浏览器会通过解析HTML文档来构建DOM树,解析CSS产生CSS规则树(CSSOM)。
- 渲染过程中,如果遇到
<script>
就停止渲染,执行JS代码。因为浏览器有GUI渲染线程与JS引擎线程,为了防止渲染出现不可预期的结果,这两个线程是互斥的关系。 JavaScript的加载、解析与执行会阻塞DOM的构建。 - 之后根据DOM树和CSS规则树构建渲染树(Render-Tree),在这个过程中CSS会根据选择器匹配HTML元素。渲染树包括了每个元素的大小、边距等样式属性,渲染树中不包含设置为
display: none;
的隐藏元素及<head>
、<script>
等不可见元素(但是对于visibility: hidden;
或opacity: 0;
的元素,它们会占据屏幕空间,因此它们将出现在渲染树中)。 最后浏览器根据元素的坐标和大小来计算每个元素的位置,并绘制这些元素到页面上。
了解了浏览器渲染机制,我们就能对重绘和回流有一个比较清晰的认识了:
-
重绘
指的是页面的某些部分要重新绘制,比如颜色或背景色的修改,元素的位置和尺寸并未改变。 -
回流
则是元素的位置或尺寸发生了改变,浏览器需要重新计算渲染树,导致渲染树的一部分或全部发生变化;渲染树重新建立后,浏览器再重新绘制页面上受影响的元素。ps:回流的代价比重绘的代价高很多,重绘不一定是因为回流,但回流一定会导致重绘。
导致回流的DOM操作举例如下:
- 页面初始化的渲染
- 增加、删除、修改或移动可见DOM元素
- 修改CSS样式或DOM改变元素内容,导致DOM元素的尺寸改变
- 浏览器窗口尺寸改变及浏览器窗口滚动
- 元素内容变化(文字数量或图片大小等等)
- 设置style属性
- 查询某些属性或调用某些方法
DOM操作影响性能的原因主要有以下两点:
- 在浏览器中通过JavaScript代码调用DOM接口,相当于两个独立模块的交互。这种跨模块的调用性能损耗比较高。
- DOM操作通常会导致浏览器的重绘和回流,重绘和回流的代价很高。