当前位置: 首页 > news >正文

《Vue3 从入门到大神26篇》Virtual DOM 与 Diff 算法 —— Vue3 的高效更新机制

前言

这一篇我们来了解vue3高效更新机制的核心逻辑,很多人对 Virtual DOM(虚拟 DOM)有一个误解:

❌ “Virtual DOM 比原生 DOM 快。”

这是一个伪命题

事实上,操作 Virtual DOM 本身也有成本,它之所以能提升性能,是因为:

它能计算出最小更新量,从而减少昂贵的 DOM 操作。

而 Vue3 的 Diff 算法,正是为了尽可能少地操作 DOM

这一篇,我们系统讲解:

  • ✅ 什么是 Virtual DOM

  • ✅ VNode 的设计

  • ✅ Vue3 的 Diff 策略

  • ✅ 最长递增子序列(LIS)在 Diff 中的作用

  • ✅ 为什么 Vue3 的 Diff 更快


一、为什么需要 Virtual DOM?

1️⃣ 原生 DOM 的问题

document.getElementById('app').innerHTML = ` <div> <p>${count}</p> <button>Click</button> </div> `

❌ 问题:

  • DOM 操作非常昂贵(重排、重绘)

  • 每次更新都重新创建所有 DOM

  • 无法复用已有 DOM


2️⃣ Virtual DOM 的核心思想

数据变化 ↓ 生成新的 Virtual DOM(JS 对象) ↓ Diff 对比新旧 VDOM ↓ 计算出最小变更 ↓ 只更新必要的真实 DOM

📌Virtual DOM 是“中间层”,不是“性能银弹”。


二、VNode:Virtual DOM 的基本单元

1️⃣ VNode 是什么?

interface VNode { type: string | object | null // 标签名 / 组件对象 props: Record<string, any> | null children: VNode[] | string | null el: HTMLElement | null // 对应的真实 DOM key?: string | number }

2️⃣ 一个真实 VNode 示例

const vnode = { type: 'div', props: { class: 'container' }, children: [ { type: 'p', props: null, children: 'Hello Vue3' } ], el: null }

3️⃣ h 函数:创建 VNode 的工具

function h(type, props, children) { return { type, props, children, el: null } } // 使用 h('div', { class: 'container' }, [ h('p', null, 'Hello Vue3') ])

📌Vue 模板编译后,最终都会变成h()调用。


三、Diff 算法:找出差异

1️⃣ Diff 的本质

给定两棵树(旧 VDOM 和新 VDOM),找出最小的修改操作。

这是一个经典的树编辑距离问题,理论上时间复杂度是O(n³)

Vue 做了一个重要假设:

只比较同一层级的节点,不跨层级比较。

这样时间复杂度降到了O(n)


2️⃣ Vue2 的双端 Diff

Vue2 使用的是双端比较算法

旧:[a, b, c, d] 新:[b, c, e, d]

比较流程:

  • 旧头 vs 新头

  • 旧尾 vs 新尾

  • 旧头 vs 新尾

  • 旧尾 vs 新头

✅ 优点:逻辑直观

❌ 缺点:移动节点时不够高效


四、Vue3 的 Diff:更聪明的策略

Vue3 在双端比较的基础上,引入了最长递增子序列(LIS)优化。

1️⃣ 核心思路

1. 预处理:处理相同的前缀和后缀 2. 构建 key -> index 映射表 3. 找出最长不需要移动的节点序列 4. 只移动必要的节点

2️⃣ 示例:为什么要 LIS?

<!-- 旧 VDOM --> <ul> <li key="a">A</li> <li key="b">B</li> <li key="c">C</li> <li key="d">D</li> </ul> <!-- 新 VDOM --> <ul> <li key="b">B</li> <li key="c">C</li> <li key="e">E</li> <li key="d">D</li> </ul>

变化:

  • A 被删除

  • E 被插入

  • B、C、D 顺序变化


3️⃣ 最长递增子序列的作用

Vue3 会找出在新旧 VDOM 中相对位置不变的节点序列

旧索引: [0, 1, 2, 3] (a, b, c, d) 新索引: [1, 2, 3, 0] (b, c, d, a) 最长递增子序列: [1, 2, 3] → 对应 b, c, d

📌结论

b、c、d 这三个节点不需要移动,只需要移动其他节点。


五、LIS 算法简化实现

function getLIS(arr) { const dp = new Array(arr.length).fill(1) let maxLen = 1 for (let i = 1; i < arr.length; i++) { for (let j = 0; j < i; j++) { if (arr[i] > arr[j]) { dp[i] = Math.max(dp[i], dp[j] + 1) } } maxLen = Math.max(maxLen, dp[i]) } // 回溯构造 LIS const lis = [] for (let i = arr.length - 1; i >= 0; i--) { if (dp[i] === maxLen) { lis.unshift(i) maxLen-- } } return lis } // 示例 console.log(getLIS([1, 2, 3, 0])) // [0, 1, 2]

📌Vue3 使用的是 O(n log n) 的贪心算法版本,这里为了直观用了 O(n²)。


六、Vue3 Diff 的核心优势

对比项

Vue2

Vue3

比较策略

双端比较

双端 + LIS

移动节点

频繁移动

最小化移动

性能

较好

更好

复杂度

O(n)

O(n),常数更小

📌Vue3 的 Diff 不是"更快",而是"更少移动 DOM"。


七、PatchFlags 与 Diff 的关系

还记得第 22 篇讲的 PatchFlags 吗?

// 编译后的渲染函数 _createElementVNode("p", null, _toDisplayString(_ctx.count), 1 /* TEXT */)

📌PatchFlags 是 Diff 的上游优化

  • PatchFlags 告诉 Diff:这个节点只有文本变化

  • Diff 就只更新文本内容,不再递归比较子节点

两者结合,效果翻倍。


八、手写一个简化的 Diff(理解版)

function diff(oldChildren, newChildren, container) { const oldKeyToIndex = {} oldChildren.forEach((child, i) => { oldKeyToIndex[child.key] = i }) const newIndices = new Array(newChildren.length).fill(-1) // 建立新节点到旧节点的映射 newChildren.forEach((child, i) => { const oldIndex = oldKeyToIndex[child.key] if (oldIndex !== undefined) { newIndices[i] = oldIndex } }) // 计算 LIS const lis = getLIS(newIndices) // 逆序遍历,移动节点 let j = lis.length - 1 for (let i = newChildren.length - 1; i >= 0; i--) { const newChild = newChildren[i] const oldIndex = newIndices[i] if (oldIndex === -1) { // 新增节点 mount(newChild, container) } else { if (i !== lis[j]) { // 需要移动 move(newChild, container) } else { // 不需要移动 j-- } } } }

九、面试高频问答

Q1:Virtual DOM 一定比原生 DOM 快吗?

不一定。VDOM 的优势在于减少 DOM 操作次数,而不是操作本身更快。

Q2:Vue3 的 Diff 和 Vue2 有什么不同?

Vue3 在双端比较基础上引入 LIS,最小化节点移动次数。

Q3:LIS 在 Diff 中的作用是什么?

找出不需要移动的节点序列,只移动必要的节点。


十、总结(原理级)

  • Virtual DOM 是描述 UI 的 JS 对象

  • Diff 的目标是最小化 DOM 操作

  • Vue3 的 Diff = 双端比较 + LIS

  • PatchFlags 是 Diff 的上游优化

  • 少移动 DOM,才是真正的快


📢 下期预告

👉第 27 篇:编译时优化揭秘 —— Vue 模板是如何变成 JS 代码的?

http://www.jsqmd.com/news/1149681/

相关文章:

  • 个人微信API二次开发,还在傻傻Hook应用层?难道不懂Android底层Binder IPC劫持机制吗?
  • 081、BasicVSR视频超分:双向传播与光流对齐的经典框架解析
  • APK安装器完全指南:在Windows上轻松安装安卓应用
  • Node.js 22 LTS 部署:3种主流Linux发行版(Ubuntu/CentOS/Alpine)安装方案实测
  • League-Toolkit:英雄联盟终极自动化助手完整配置指南
  • SubAgent设计三铁律:状态隔离、原子通信与契约化错误处理
  • ArcGIS 10.5 安装与许可机制深度解析
  • macOS启动盘制作全指南:镜像获取、createinstallmedia命令与安全授权
  • 程序员开会记笔记:2026年3款记灵音频转文字工具 自动生成纪要
  • 计算机毕业设计之基于ssm框架的在线影视网站的设计与实现
  • VSCode + Roo Cline:科研MCP本地智能代理服务搭建指南
  • openclaw macOS设备模拟框架安装与驱动配置指南
  • 超算平台部署Abaqus的glibc兼容性与Slurm调度实践
  • 如何让学术书籍封面设计不再头疼?ElegantBook模板终极指南
  • 5个简单步骤:用XHS-Downloader轻松保存小红书上的任何内容
  • 如何3分钟掌握Android无线投屏:Escrcpy终极图形化控制指南
  • OpenMetadata Python元数据摄取环境搭建避坑指南
  • 2026年AI编程工具安装指南:本地推理、上下文中间件与安全策略
  • 国内开发者Gemini高可用接入实战:Nginx三层架构方案
  • 还在愁论文框架搭不好?9款AI写作辅助网站一键生成覆盖全学科
  • LangChain生产实战:LCEL链式编程与RAG检索优化指南
  • 超算环境下COMSOL静默安装与setupconfig.ini深度配置指南
  • ArchLinux 2026.1萌新向安装指南:从连通性验证到桌面可用
  • EasyTier内网设备直连与安全组网实践指南
  • OpenClaw部署本质:构建可信消息通信链路的实战指南
  • JDK17生产级部署指南:跨平台兼容性与运行环境治理
  • JDK17安装部署全指南:信创适配、多架构支持与环境变量避坑
  • AutoCAD 2024安装失败根源:Windows系统级环境预检指南
  • TLE6208与PIC18F97J60的直流电机控制方案详解
  • Superpowers协议解析:AI编程中的能力调度中枢