操作真实 DOM 有多贵?

发布时间:2026/6/28 17:49:18
操作真实 DOM 有多贵? 先看一段代码// 把一个 div 的背景色改成红色 document.getElementById(box).style.backgroundColor red你觉得这一行代码的执行成本是多少答案远比你想象的复杂1. JS 引擎找到 DOM 节点 2. 修改 DOM 节点的 style 属性 3. 浏览器标记这个节点需要重新计算样式Recalculate Style 4. 重新布局Layout/Reflow—— 可能影响周围元素的位置 5. 重新绘制Paint—— 把新的颜色画到屏幕上 6. 合成Composite—— 把各层合成最终画面修改一个属性可能触发整个渲染流水线。如果是 1000 个属性修改呢如果是添加、删除、移动几百个节点呢直接操作真实 DOM 就像在沙滩上写字——每一笔都很简单但改一个字就可能要抹平整个沙滩。虚拟 DOM 是什么虚拟 DOM 就是用普通的 JavaScript 对象来描述一个 DOM 节点。真实 DOMdiv idapp classcontainer pHello/p /div等价的虚拟 DOM{ tag: div, props: { id: app, class: container }, children: [ { tag: p, props: {}, children: [ { tag: undefined, text: Hello } // 文本节点 ] } ] }Vue 中把这个 JS 对象叫做VNodeVirtual Node。为什么不用真实 DOM要自己造一个用一张表来对比真实 DOM虚拟 DOM (VNode)本质C 实现的浏览器对象普通 JS 对象创建成本高创建几百个属性低就几个字段操作成本高可能触发回流低只是改 JS 对象跨平台只能在浏览器可以渲染到不同平台可控性浏览器说了算框架完全控制核心思想用廉价的 JS 对象操作代替昂贵的 DOM 操作。先在 JS 层面做完所有计算最后一次性、最少化地更新真实 DOM。打个比方装修房子你要重新装修一个房间有两种方式方式一直接施工直接操作 DOM把左边这面墙砸掉 → 工人开始砸 等等右边那面也砸 → 工人换位置砸 不对左边还是留着吧 → 工人每次指示都立刻执行改主意了就返工。工期长、成本高。方式二先在图纸上画虚拟 DOM在图纸上画一遍 → 对比旧图纸 → 标记出所有改动 → 一次施工完成先在纸上JS 内存把所有方案画好确认无误后列出最小改动清单一次性施工。虚拟 DOM 就是那张设计图纸。Diff 算法怎么找出最小改动现有一棵旧的 VNode 树和一棵新的 VNode 树怎么找出最少改动把两棵树完全比较的时间复杂度是O(n³)——对一棵有 1000 个节点的树来说这是 10 亿次比较不可接受。但前端有一个重要的观察大部分情况下跨层级的移动非常罕见。基于这个假设Vue和 React的 diff 算法做了一个简化只比较同一层级的节点。不同层级的直接替换不尝试移动到另一层。这样算法退化到O(n)即每个节点只比较一次。同层 Diff 的三个步骤Vue 的 diff 采用的是双端比较策略。以下以子节点数组的 diff 为例。假设旧子节点是[A, B, C, D]新子节点是[B, A, D, E]。步骤 1头头比较旧: [A, B, C, D] ↑ 新: [B, A, D, E] ↑ A ! B → 不匹配结束头头比较步骤 2尾尾比较旧: [A, B, C, D] ↑ 新: [B, A, D, E] ↑ D ! E → 不匹配结束尾尾比较步骤 3头尾交叉比较旧头 vs 新尾: A vs E → 不匹配 旧尾 vs 新头: D vs B → 不匹配此时四个指针都没匹配上说明需要更复杂的操作。Vue 会尝试在旧节点中查找新节点是否存在通过 key。如果设置了 keydiv v-foritem in list :keyitem.idkey 的作用就是给每个 VNode 一个唯一的身份标识让 diff 算法能识别出这个节点只是位置变了不是被删除重建了。旧: [{key:A}, {key:B}, {key:C}, {key:D}] 新: [{key:B}, {key:A}, {key:D}, {key:E}] 有 key 时 B 在旧节点中找到 → 移动位置即可 A 在旧节点中找到 → 移动位置即可 D 在旧节点中找到 → 移动位置即可 E 不在旧节点中 → 新建