前端操作 DOM ,为什么最耗性能?

不一定

  1. 预备知识

我们看一下浏览器和 JS 引擎的关系(以 Chrome 和 V8 举例):

V8 binding between WebKit and V8

其他浏览器和 JS 引擎也大同小异。

在 ECMAScript 规范中,Web IDL 定义了 DOM 和 JS Object 的对应关系:

Web IDL、DOM、ECMAScript

通常而言,DOM 对象是使用 C++ 开发的,而通过 V8 Binding,在 V8 引擎内会有一个和 DOM 对象对应的 JS 对象,我们称之为Wrapper objects(包装对象)。

包装对象和原生对象的关系可能是 1 对 1,也可能是 n 对 1,但是会是 1 对 n。

2. 为什么有时候 DOM 不慢

从上面的介绍可以看出,DOM 是 C++ 写的,性能肯定不慢。而且 V8 Binding 会把原生 DOM 对象映射为包装的 JS 对象。因此,我们操作 DOM 和操作 JS 对象是一样的。

我们以 document 为例。

js 普通对象:

let o = {a: 2};

console.time('js');
for(let i=0; i<=1e4; i++)
    o.a = i;
console.timeEnd('js');
// 耗时: 0.31396484375ms

document 对象:

console.time('dom');
for(let i=0; i<=1e4; i++)
    document.a = i;
console.timeEnd('dom');
// 耗时: 0.302978515625ms

从上面的结果看,DOM 对象好像比 JS 普通对象还要快一些。

3. DOM 为什么慢

现在我们把上面的测试代码改一下,把 document.a 改成 document.title

console.time('dom');
for(let i=0; i<=1e4; i++)
    document.title = i;
console.timeEnd('dom');
// 耗时: 128.337646484375ms

耗时突然变成了原来的 400 多倍。

这是因为 JS 对象的属性(properties)映射到了 DOM 对象的特性(attributes)上。当我们修改 document.a 时,只是修改了普通的 JS 对象,但是在我们修改 document.title 的时候,同时也修改了 DOM 对象的 attributes。如果我们在 Chrome Devtool 运行这段代码,可以看到页面的标题在不停的改变,代码运行结束后,页面标题变成了 10000。

性能消耗在 JS 对象和 DOM 对象的转换和同步。也就是 V8XXX::toNative()::toV8() 的调用。

4. CSS

浏览器除了具有 JS 引擎外,还有排版引擎。而如果 JS 在操作 DOM 时修改了 CSS,那么性能就会再一次降低

即使我们不修改 DOM 对象的 CSS,仅仅是读取样式值,也有可能会引起 relayout。

5. GC

DOM 对象还会导致 GC 变复杂。

虽然 DOM 的性能有可能比普通对象还快,但是在 99% 的场景下 DOM 对象是慢的。

THE END