虚拟 DOM 的性能到底如何
讨论虚拟 DOM 之前,需要了解 命令式代码、声明式代码
1 命令式和声明式
前端开发中,存在两种代码方式:
- 命令式
- 声明式
1.1 命令式
命令是的一大特点就是,关注过程
例如,我现在要为 div 设置文本内容,并添加点击事件
1 | const div = document.getElementById('container'); // 获取 Dom |
可以看到,使用原生 JS,关注于一步一步的过程:
- 拿到 div
- 修改 innerText
- 为元素添加事件监听
1.2 声明式
而声明式的特点是,关注结果
结合 Vue ,来实现上面的功能
1 | <div @click="() => {console.log('触发了点击事件')}"> New Word </div> |
这种代码,不关心具体是怎样实现的,不需要获取 Dom 来做进一步的操作,我们只关心结果
相当于告诉 Vue:”帮我给 div 节点加上文本内容,添加上点击事件“
而具体的过程,是 Vue 封装好
所以:Vue 暴漏给用户的是 声明式,内部是 命令式
1.3 命令式和声明式的优缺点
在复杂页面开发的情况下,相比于声明式,命令式显然书写相对繁琐一点。显然使用声明式代码可维护性较强一些。
那么命令式和声明式的性能谁更优呢?
声明式代码的性能不一定优于命令式代码的性能
例如上面的示例:
命令式代码,直接使用命令操作,修改文本内容:
1 | div.innerText = 'New Word'; // 直接修改 |
声明式代码:
1 | <!-- 之前: --> |
使用声明式代码,其实框架需要找到前后的差异,只更新变化的地方,然后执行 div.innerText = 'New Word'; 进行修改,完成修改文本内容
如果将修改的性能消耗定义为 A,把找出差异的性能消耗定义为 B,可以得出如下:
- 命令式代码的更新性能消耗 = A
- 声明式代码的更新性能消耗 = A + B
这符合前面提到的:声明式代码的性能不一定优于命令式代码的性能
但是如果能将 B 的性能消耗降低,无限接近 0,声明式和命令式的性能消耗就能相等
而能够降低性能 B 的方式,就是虚拟 DOM 了
所谓的虚拟 DOM,就是最小化找出差异的性能消耗,使声明式代码性能消耗 接近 命令式代码的性能消耗。
2 虚拟 DOM 创建页面的性能消耗
为了比较虚拟 DOM 的性能
需要了解其创建、更新的过程
我们都知道,虚拟 DOM 实际上是 JS 对象,所以:
虚拟 DOM 创建页面的效率 = 创建 JS 对象的效率 + 新建所有 DOM 元素的效率
| 虚拟 DOM 创建页面的效率 | |
|---|---|
| 纯 JS 层面计算 | 创建 JS 对象的效率 |
| DOM 层面计算 | 新建所有 DOM 元素的效率 |
为了比较纯 JS 层面的计算消耗,这里使用 JavaScript 代码执行效率对比工具进行测试
上面是纯 JS 层面的计算,模拟创建虚拟 DOM,循环 1000 次
下面则是 DOM 操作,循环 1000 次,每创建一个对象,作为子节点插入
根据 Relative Rate,纯 JS 层面的计算的效率要远远高于DOM 操作
从宏观上来看,则认为 虚拟 DOM 创建页面的效率 和 直接采用真实 DOM ,是没有差异的
2 虚拟 DOM 更新页面的性能消耗
上面讨论了虚拟 DOM 创建页面时的性能情况,或许可以认为和直接操作真实DOM没有差异,甚至深究下,虚拟 DOM 的性能更差
但是在更新界面的情况下,性能的提升就明显了
如果我们使用 innerHtml 更新界面
1 | div.innerHTML = 'New Word' |
其过程是,重新构建 HTML 字符串,再重新设置 DOM 元素的 innerHTML 的属性
也就是:那么我们只改了一个字,也要销毁所有旧的 DOM 元素,再全量创建新的 DOM 元素
而虚拟 DOM 更新页面得过程:
它需要重新构建 JS 对象(虚拟DOM),通过 Diff 算法,寻找差异,复用旧 DOM,只找到更新的元素进行修改
| 虚拟 DOM | innerHTML | |
|---|---|---|
| 纯 JS 层面计算 | 创建 JS 对象的效率 + Diff | 渲染 HTML 字符串 |
| DOM 层面计算 | 仅更新变化的 DOM | 销毁所有旧 DOM + 新建所有新 DOM |
即使虚拟 DOM 多出 构建 JS 对象(虚拟DOM),通过 diff 算法的两个步骤
但他们都是 纯 JS 层面的计算,经过上面的测试,不会产生数量级的消耗
再观察 DOM 层面的消耗
虚拟 DOM 仅更新必要的元素
但 innerHTML 需要全量更新,页面内容越多,性能消耗越大
这时虚拟 DOM 的优势就体现出来了
3 总结
根据命令式和声明式代码的原理,我们能得知:
声明式代码的更新性能消耗 = 修改的性能消耗定义 + 找出差异的性能消耗
可以认为虚拟 DOM 能够使声明式代码性能消耗 接近 命令式代码的性能消耗
经过测试纯 JS 层面计算消耗和 DOM 层面的计算消耗,能确定在宏观角度上,虚拟 DOM 在创建页面的消耗差异不大,在更新界面的情况下,如果页面内容越多,获得的性能提升也会更好
同时,纯 JS 层面的少量计算消耗,与获得较好的代码维护性上来看,虚拟 DOM 是一个不错的选择
参考:《vue.js设计与实现》霍春阳 著作
About this Post
This post is written by Duan WeiCheng, licensed under CC BY-NC 4.0.