Vue/React 采用虚拟 dom 的优势,虚拟 dom 一定比直接操作 dom 更快吗?
浏览器直接操作 DOM 比通过虚拟 DOM(Virtual DOM)更新更慢,这一结论并非绝对,而是在频繁、复杂的 DOM 操作场景下的普遍现象。其核心原因在于 DOM 本身的 “重量级” 特性、浏览器渲染机制的开销,以及虚拟 DOM 通过 “批量优化” 和 “最小化操作” 规避了这些开销。以下从底层原理详细分析:
一、DOM 的 “重量级” 本质
DOM(文档对象模型)是浏览器提供的一套 API,用于操作 HTML/XML 文档的结构。但 DOM 节点并非单纯的 JavaScript 对象,它具有以下特性,导致直接操作成本极高:
-
多线程交互开销
- JavaScript 运行在浏览器的 “主线程” 中,而 DOM 操作会触发浏览器的 “渲染线程” 工作(如重排、重绘)。主线程与渲染线程是互斥的(同一时间只能有一个线程运行),频繁的 DOM 操作会导致线程频繁切换,产生额外开销。
- 例如:直接执行
document.createElement后立即修改其样式,会触发主线程与渲染线程的同步,比操作纯 JavaScript 对象(如虚拟 DOM 节点)慢得多。
-
DOM 节点的复杂属性
- 每个 DOM 节点包含大量属性和方法(如
offsetParent、getBoundingClientRect等),这些属性的计算依赖于布局环境,访问或修改时会触发浏览器的 “重排”(Reflow,计算元素几何位置)或 “重绘”(Repaint,更新元素像素),而这两个过程是极其耗时的:- 重排:涉及整个渲染树的几何计算,若页面元素多(如列表、表格),可能导致整个页面布局重新计算,耗时可达毫秒级。
- 重绘:不改变几何位置,但需重新绘制元素(如修改颜色),虽比重排快,但频繁触发仍会累积开销。
- 每个 DOM 节点包含大量属性和方法(如
二、直接操作 DOM 的 “低效模式”
若直接通过原生 API 操作 DOM(如 appendChild、innerHTML 等),在频繁更新场景下会暴露以下问题:
-
高频次零散操作
- 例如:循环修改 100 个列表项的文本,若每次循环都直接修改
textContent,会触发 100 次重排 / 重绘。而浏览器对重排的优化(如 “重排队列” 批量处理)并非总能生效(例如访问offsetHeight会强制刷新队列,导致即时重排)。
- 例如:循环修改 100 个列表项的文本,若每次循环都直接修改
-
无差别的全量更新
- 若数据变化后直接用
innerHTML覆盖整个列表(如ul.innerHTML = newHTML),即使只有一个元素变化,也会销毁所有旧 DOM 节点并创建新节点,导致大量不必要的内存回收和重建开销,同时丢失元素的状态(如输入框的用户输入)。
- 若数据变化后直接用
-
缺乏 “最小化操作” 计算
- 直接操作 DOM 时,开发者需手动计算 “新旧 DOM 的差异”,才能确定最小修改范围(如只改一个元素的文本)。但手动优化成本极高,尤其在复杂组件中容易出现冗余操作(如删除再添加相同元素)。
三、虚拟 DOM 的 “优化机制”
虚拟 DOM 是用 JavaScript 对象模拟的 DOM 结构(如 { tag: 'div', props: { id: 'app' }, children: [] })。它的核心价值并非 “比 DOM 快”,而是通过批量处理和最小化更新,规避了直接操作 DOM 的低效场景:
-
将多次 DOM 操作转为一次批量操作
- 虚拟 DOM 会先在 JavaScript 层面收集所有数据变化(如多次修改列表项),生成完整的 “新虚拟 DOM”,再通过
diff算法计算与 “旧虚拟 DOM” 的差异,最终将所有差异合并为一次 DOM 操作批量执行。 - 例如:100 次文本修改,虚拟 DOM 只会触发 1 次重排 / 重绘(而非 100 次),大幅减少线程切换和渲染开销。
- 虚拟 DOM 会先在 JavaScript 层面收集所有数据变化(如多次修改列表项),生成完整的 “新虚拟 DOM”,再通过
-
精准计算 “最小差异”
- 通过
diff算法(如 Vue3 的 PatchFlags + 最长递增子序列),虚拟 DOM 能定位到 “必须修改的最小范围”(如只更新某个元素的文本,而非整个列表),避免全量删除 / 重建。 - 例如:列表中仅一个元素的文本变化,虚拟 DOM 会直接找到该元素并修改
textContent,而不是用innerHTML覆盖整个列表。
- 通过
-
隔离 DOM 操作与渲染时机
- 虚拟 DOM 的计算完全在 JavaScript 线程中进行(不涉及渲染线程),可通过 “微任务” 或 “调度器”(如 Vue 的
nextTick、React 的Scheduler)将 DOM 更新推迟到合适的时机(如浏览器空闲时),避免阻塞主线程。
- 虚拟 DOM 的计算完全在 JavaScript 线程中进行(不涉及渲染线程),可通过 “微任务” 或 “调度器”(如 Vue 的
四、特殊场景:虚拟 DOM 未必更快
虚拟 DOM 的优势是 “在复杂、频繁更新场景下的稳定性”,但在以下场景中,直接操作 DOM 可能更快:
-
单次简单操作
- 例如:修改单个元素的文本,直接
el.textContent = 'new'比 “创建虚拟 DOM → diff → 执行 DOM 操作” 的流程更短(少了 JavaScript 层面的计算开销)。
- 例如:修改单个元素的文本,直接
-
可预测的局部更新
- 若开发者能精确知道哪些 DOM 节点需要更新(如通过 ID 直接定位),手动操作可能比虚拟 DOM 的
diff算法更高效(省去diff的计算成本)。
- 若开发者能精确知道哪些 DOM 节点需要更新(如通过 ID 直接定位),手动操作可能比虚拟 DOM 的
总结
浏览器直接渲染 DOM 比虚拟 DOM 慢的核心原因是:DOM 操作本身涉及重排 / 重绘等高额开销,而直接操作容易导致高频次、全量更新;虚拟 DOM 通过 JavaScript 层面的批量计算和差异优化,将零散、冗余的 DOM 操作转化为最少、最必要的批量操作,从而规避了这些开销。
虚拟 DOM 的价值并非 “速度”,而是 “用可接受的 JavaScript 计算成本,换取 DOM 操作的稳定性和开发效率”—— 让开发者无需手动优化 DOM 操作,也能在大多数场景下获得不错的性能。
更多推荐



所有评论(0)