1.3 虚拟 DOM 的性能到底如何
虚拟 DOM 的意义不是「比原生 DOM 快」,而是用很小的运行时开销,换到接近最优的更新效果与更好的可维护性。
一、虚拟 DOM 的定位
声明式代码的更新性能消耗 = 找出差异的性能消耗 + 直接修改的性能消耗。
- 只要把「找出差异」这一步的开销最小化,声明式代码的性能就能无限接近命令式代码。
- 虚拟 DOM 正是为此而生的技术。
采用虚拟 DOM 的更新技术,性能理论上不可能比极致优化的原生 JavaScript 操作 DOM 更高。
但实战中很难写出绝对优化的命令式代码,尤其是应用规模变大时;即使写出来了,投入产出比也很低。所以虚拟 DOM 的价值在于用可控的成本稳定拿到不错的性能。
二、创建页面时的对比(innerHTML vs 虚拟 DOM)
| 层面 | 对比结论 |
|---|---|
| JavaScript 计算 | 两者差距不大 |
| DOM 计算 | 两者差距不大(都要新建所有 DOM 元素) |
只看数量级差异:处于同一数量级就认为没有差异。创建页面时二者性能接近。
三、更新页面时的对比
innerHTML 的更新过程
- 重新构建 HTML 字符串;
- 重新设置 DOM 元素的
innerHTML属性。
第 2 步等价于销毁所有旧的 DOM 元素,再全量创建新的 DOM 元素。
虚拟 DOM 的更新过程
- 重新创建 JavaScript 对象(新的虚拟 DOM 树);
- 比较新旧虚拟 DOM;
- 找到变化的元素并更新它。
性能差异来源
| 层面 | 虚拟 DOM | innerHTML |
|---|---|---|
| JavaScript 运算 | 多出一次 Diff 的开销,但同属 JS 层运算,不产生数量级差异 | 字符串拼接 |
| DOM 运算 | 只更新必要的元素 | 全量销毁 + 重建 |
| 与页面规模的关系 | 与页面大小基本无关 | 页面越大,更新开销越大 |
关键差异不在 JS 层,而在 DOM 层:虚拟 DOM 的更新量只取决于变化的内容,innerHTML 的更新量取决于整个页面。
四、三方方案总结:心智负担、可维护性、性能
| 方案 | 心智负担 | 可维护性 | 性能 |
|---|---|---|---|
| 原生 DOM 操作 | 巨大,需手动创建/删除/修改大量元素 | 极差 | 最高 |
| innerHTML | 有一定负担,事件绑定仍要靠原生 JS | 一般 | 模板大时最差 |
| 虚拟 DOM | 小 | 强 | 不如极致优化的原生 JS,但在可维护的前提下相当不错 |
- 原生 DOM 操作:性能天花板,但代价是巨大的心智负担与极差的可维护性。
- innerHTML:省去了手动创建元素,但事件绑定仍需原生 JS,且模板越大更新越慢。
- 虚拟 DOM:在「心智负担 + 可维护性 + 性能」三者之间取得了最好的平衡,这是 Vue.js 选择它的原因。
相关
- 1.2 性能与可维护性的权衡:
B + A公式与虚拟 DOM 的由来。 - 1.4 运行时和编译时:编译阶段还能进一步降低运行时的开销。