资讯中心

虚拟 DOM 与 Diff 算法

📅 2026/6/26 23:58:37
虚拟 DOM 与 Diff 算法
文章目录前言一、什么是虚拟 DOM1.1 定义1.2 为什么需要1.3 并非总是更快二、VNode 结构2.1 基本字段2.2 常见类型三、更新流程四、Vue 2 双端 Diff4.1 算法思路4.2 特点五、Vue 3 快速 Diff5.1 为什么放弃双端 Diff5.2 快速 Diff 流程列表5.3 最长递增子序列LIS六、Vue 3 编译时优化概览6.1 PatchFlag标记动态节点6.2 Block Tree扁平化动态节点6.3 静态提升七、key 与 Diff 的关系简述八、与 React 的对比九、面试聚焦9.1 虚拟 DOM 并非总是更快9.2 Vue 3 为什么改 Diff9.3 PatchFlag 做什么9.4 Block Tree 是什么十、易混淆点十一、思考与练习总结前言虚拟 DOM 是 Vue 渲染层的核心机制用 JavaScript 对象描述 DOM通过 Diff 算法找出最小变更并批量更新。本篇会讲清楚VNode 结构与更新流程Vue 2 双端 Diff vs Vue 3 快速 Diff最长递增子序列LIS与编译时优化概览一、什么是虚拟 DOM1.1 定义虚拟 DOM 是用 JavaScript 对象描述真实 DOM 结构的轻量级表示。状态变化时生成新的 VNode 树与旧树 Diff 后只把差异应用到真实 DOM。// 真实 DOMdivclassboxh1Hello/h1/div// 虚拟 DOMVNode 示意constvnode{type:div,props:{class:box},children:[{type:h1,children:Hello}]}1.2 为什么需要问题虚拟 DOM 的解决直接操作 DOM 慢且难追踪声明式描述 UI框架算最优更新多次状态变更多次 DOM 操作合并为一次批量更新跨平台Web / SSR / 小程序同一套 VNode 可对接不同渲染器1.3 并非总是更快极少量的 DOM 更新时手动改 DOM 可能更快。虚拟 DOM 的价值在于复杂场景下的声明式开发、批量更新和跨平台抽象而不是「一定比原生 DOM 快」。二、VNode 结构2.1 基本字段constvnode{type:div,// 标签名、组件、Fragment 等props:{class:box},children:[],key:unique-id,// 列表 Diff 用el:null,// 运行时关联的真实 DOMpatchFlag:0,// Vue 3 编译时标记见下文dynamicChildren:null// Vue 3 Block Tree 动态子节点}2.2 常见类型// 文本{type:Text,children:hello}// 元素{type:div,props:{},children:[]}// 组件{type:MyComponent,props:{msg:hi}}// Fragment多根节点{type:Fragment,children:[...]}三、更新流程响应式数据变化 ↓ 触发组件 re-render生成新 VNode 树 ↓ 新旧 VNode 树 Diffpatch ↓ 计算出最小变更集增删改移 ↓ 批量应用到真实 DOM// 简化 Diff 思路functionpatch(oldVNode,newVNode){if(oldVNode.type!newVNode.type){// 类型不同 → 替换节点replaceNode(oldVNode,newVNode)return}// 同类型 → 比 props、比 childrenpatchProps(oldVNode,newVNode)patchChildren(oldVNode,newVNode)}四、Vue 2 双端 Diff4.1 算法思路Vue 2 对同级列表使用双端比较新旧数组各设头尾指针从两端向中间同时比较尽量复用 DOM。旧: A B C D ↑ ↑ oldStart oldEnd 新: D A B C ↑ ↑ newStart newEnd 比较顺序共 4 种 1. oldStart vs newStart 2. oldEnd vs newEnd 3. oldStart vs newEnd 4. oldEnd vs newStart 都不匹配 → 用 key 在旧列表中查找4.2 特点适合列表头尾增删、反转等常见场景依赖key做节点身份识别最坏情况仍需较多比较Vue 3 做了进一步优化五、Vue 3 快速 Diff5.1 为什么放弃双端 DiffVue 3 借鉴 Inferno 的快速 Diff目标减少不必要的节点比较次数用最长递增子序列LIS优化列表移动操作少做 DOM insert/move5.2 快速 Diff 流程列表1. 从头同步新旧节点 type key 相同则 patch不同则停 2. 从尾同步同上从尾部向前 3. 中间段用 key → index 映射处理新增、删除、移动 4. 移动优化对需要移动的节点求 LISLIS 内节点不移动其余按需 insert5.3 最长递增子序列LIS旧: [A, B, C, D, E] 新: [A, C, D, B, E] B 从 index 1 移到 index 3 需要移动的: B LIS 帮助找出「已经相对有序、不必动」的节点减少 DOM 移动次数LIS 让 Diff 在「乱序但可复用」的列表里用最少 DOM 移动完成更新。六、Vue 3 编译时优化概览Diff 之外Vue 3 在编译阶段减少需要 Diff 的节点量6.1 PatchFlag标记动态节点编译器分析模板给 VNode 打上「哪里会变」的标记// 编译结果示意createElementVNode(div,{class:static},createTextVNode(hello,PatchFlags.TEXT)// 仅文本会变)PatchFlag含义TEXT (1)动态文本CLASS (2)动态 classSTYLE (4)动态 stylePROPS (8)动态 props…组合标记Diff 时若 patchFlag 为 0可跳过该节点子树比较有标记则只比较标记部分。6.2 Block Tree扁平化动态节点将模板中的动态节点收集到dynamicChildren数组Diff 时只遍历动态节点静态子树整段跳过。div p静态标题/p !-- 静态不参与 Diff -- p{{ msg }}/p !-- 动态进入 dynamicChildren -- span{{ count }}/span !-- 动态进入 dynamicChildren -- /div6.3 静态提升纯静态节点提升到 render 函数外只创建一次后续 render 直接复用避免重复生成 VNode。编译优化的细节PatchFlag 类型、Block 收集规则等在编译优化专题中展开。七、key 与 Diff 的关系简述列表 Diff 依赖key判断「同一节点」相同 key 相同 type → patch复用 DOM不同 key → 销毁旧节点创建新节点key 不稳定如用 index 做增删会导致错误复用。key 的完整原理见专题「Key 的作用与原理」。八、与 React 的对比对比项Vue 3ReactDiff 粒度组件级 Block 内动态节点Fiber 可中断的增量 Diff列表算法快速 Diff LIS单端 key 映射编译优化PatchFlag、静态提升、Block Tree部分优化策略不同两者都用虚拟 DOM但 Diff 策略和编译优化路径不同。九、面试聚焦9.1 虚拟 DOM 并非总是更快简单场景手动 DOM 可能更快虚拟 DOM 的价值是声明式、批量更新、跨平台。9.2 Vue 3 为什么改 Diff双端 Diff 在复杂列表下比较次数仍偏多快速 Diff LIS 减少移动配合 PatchFlag / Block Tree 减少比较范围。9.3 PatchFlag 做什么编译期标记 VNode 哪些部分动态Diff 时跳过静态内容只更新标记位。9.4 Block Tree 是什么把动态节点扁平收集Diff 只比 dynamicChildren静态子树整段跳过。十、易混淆点虚拟 DOM ≠ 更快是开发模型和批量更新策略不是性能银弹。Diff 只做同级比较不会跨层级移动节点O(n) 层级比较。Vue 2 / Vue 3 列表 Diff 不同Vue 3 用快速 Diff LIS。PatchFlag 是编译产物手写 render 函数默认无此优化。Shadow DOM ≠ Virtual DOM前者是 Web Components 浏览器封装与框架 VNode 无关。十一、思考与练习1.虚拟 DOM 的更新流程是什么解析数据变 → 新 VNode → 与旧 VNode Diff → 最小变更 → 更新真实 DOM。2.Vue 2 和 Vue 3 列表 Diff 有何不同解析Vue 2 双端比较Vue 3 快速 Diff头尾同步 中间 key 映射 LIS 优化移动。3.LIS 在 Diff 中的作用解析找出相对有序、不必移动的节点减少 DOM insert/move 次数。4.PatchFlag 和 Block Tree 解决什么问题解析减少 Diff 范围——只比动态节点、只更新标记为动态的部分静态内容跳过。5.为什么说虚拟 DOM 不总是更快解析创建 VNode 和 Diff 本身有开销极简单更新直接改 DOM 可能更省。总结虚拟 DOMJS 对象描述 DOM声明式 批量更新 跨平台更新流程新 VNode → Diff → 最小 patch → 真实 DOMVue 2双端 Diff依赖 keyVue 3快速 Diff LISPatchFlag、Block Tree、静态提升减少 Diff 量本质虚拟 DOM 是工程权衡不是「一定比原生 DOM 快」