虚拟 DOM 的 diff 算法 | 源码解析系列 3.7

一、引言
虚拟DOM diff算法是现代前端框架的核心技术之一。Vue3的diff算法在Vue2的基础上进行了大量优化,使得更新性能显著提升。本文将深入解析Vue3中虚拟DOM diff算法的实现原理,包括同层级比较、key的作用、patchFlags优化等方面。
二、diff算法概述
2.1 什么是diff算法
diff算法用于比较新旧两个虚拟DOM树的差异,并只将差异应用到真实DOM上。相比于直接操作真实DOM,虚拟DOM diff可以减少DOM操作次数,提高渲染性能。
// 旧VNode树
const oldTree = {
type: 'div',
children: [
{ type: 'span', key: '1', children: 'A' },
{ type: 'span', key: '2', children: 'B' }
]
}
// 新VNode树
const newTree = {
type: 'div',
children: [
{ type: 'span', key: '2', children: 'B' },
{ type: 'span', key: '1', children: 'A' }
]
}
// diff后只需要移动节点位置,不需要重新创建
2.2 Vue3的diff策略
Vue3采用了以下diff策略:同层级比较,只比较同级节点;使用key优化节点复用;使用patchFlags进行精确更新。
三、diff的核心函数
3.1 patchChildren
patchChildren是比较子节点的核心函数。
// packages/runtime-core/src/renderer.ts
function patchChildren(
n1: VNode,
n2: VNode,
container: HostElement,
anchor: HostNode,
parentComponent: ComponentInternalInstance,
parentSuspense: SuspenseBoundary,
isSVG: boolean,
optimized: boolean
) {
const c1 = n1.children
const c2 = n2.children
// 判断子节点类型
const isOldText = isString(c1)
const isNewText = isString(c2)
if (isOldText && isNewText) {
// 文本 -> 文本
if (c1 !== c2) hostSetText(container, c2 as string)
} else if (isArray(c1) && isArray(c2)) {
// 数组 -> 数组,使用diff算法
patchKeyedChildren(c1, c2, container, anchor, parentComponent, parentSuspense, isSVG, optimized)
} else {
// 其他情况:卸载旧的,安装新的
if (isOldText) hostRemove(container)
if (isNewText) hostSetElement(container, c2 as string)
}
}
3.2 patchKeyedChildren
patchKeyedChildren是处理带key数组的核心函数。
// patchKeyedChildren实现
function patchKeyedChildren(
c1: VNode[],
c2: VNode[],
container: HostElement,
anchor: HostNode,
parentComponent: ComponentInternalInstance,
parentSuspense: SuspenseBoundary,
isSVG: boolean,
optimized: boolean
) {
let i = 0
const l2 = c2.length
// 旧子节点的末尾索引
let e1 = c1.length – 1
// 新子节点的末尾索引
let e2 = l2 – 1
// 从左侧开始比较
while (i <= e1 && i <= e2) {
const n1 = c1[i]
const n2 = c2[i]
if (isSameVNodeType(n1, n2)) {
// 类型相同,递归patch
patch(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized)
} else {
break
}
i++
}
// 从右侧开始比较
while (i <= e1 && i <= e2) {
const n1 = c1[e1]
const n2 = c2[e2]
if (isSameVNodeType(n1, n2)) {
patch(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized)
} else {
break
}
e1—
e2—
}
// 处理新增和删除
// …
}
四、key的作用
4.1 key的原理
key帮助Vue识别哪些节点是相同的,从而实现节点的复用和移动。
// 有key的情况
// 旧:[A(key=1), B(key=2)]
// 新:[B(key=2), A(key=1)]
// diff结果:只需要移动位置
// 无key的情况
// 旧:[A, B]
// 新:[B, A]
// diff结果:可能需要销毁A和B,重新创建
4.2 key的最佳实践
<!– 正确使用key –>
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
<!– 避免使用index作为key –>
<li v-for="(item, index) in items" :key="index">{{ item.name }}</li>
五、diff算法的优化
5.1 静态提升
Vue3编译器会识别静态内容并进行提升。
// 模板
<div>
<span>静态内容</span>
<p>{{ dynamic }}</p>
</div>
// 编译后
const hoisted = h('span', null, '静态内容')
function render() {
return h('div', null, [
hoisted, // 复用,不参与diff
h('p', null, dynamic)
])
}
5.2 patchFlags
patchFlags用于标记需要更新的部分。
// patchFlags类型
const PatchFlags = {
TEXT: 1, // 文本更新
CLASS: 2, // class更新
STYLE: 4, // style更新
PROPS: 8, // 属性更新
FULL: 16, // 完整props
DIFF: 128 // 完整diff
}
// 带patchFlag的VNode
h('div', { patchFlag: PatchFlags.TEXT }, message)
六、diff过程详解
6.1 首尾比较
从数组两端向中间比较。
// 首尾比较示例
// 旧:[A, B, C, D]
// 新:[B, C, D, A]
// 第一轮:比较头部
// A vs B -> 不相同,停止
// 第二轮:比较尾部
// D vs A -> 不相同,停止
// 中间部分需要移动
6.2 移动操作
当节点需要移动时的处理。
// 移动操作
function moveNode(
vnode: VNode,
container: HostElement,
anchor: HostNode,
parentComponent: ComponentInternalInstance,
moveType: number
) {
// 获取当前el
const el = vnode.el
// 移动到锚点前面
hostInsert(el, container, anchor)
}
七、性能优化
7.1 虚拟列表
对于大量列表项,可以使用虚拟列表优化。
// 虚拟列表原理
// 只渲染可视区域内的元素
// 滚动时动态替换渲染内容
7.2 避免不必要的diff
// 使用v-memo
<li v–for="item in items" :key="item.id" v–memo="[item.selected]">
{{ item.content }}
</li>
八、总结
本文深入解析了Vue3中虚拟DOM diff算法的实现原理。Vue3采用同层级比较的策略,使用key来优化节点复用,通过patchFlags实现精确更新。diff算法从数组两端向中间比较,找出需要添加、删除和移动的节点,然后进行最小化的DOM操作。
理解diff算法对于优化Vue应用性能和解决渲染问题非常重要。后续我们将学习组件更新机制与优化、生命周期钩子等内容。
参考资料
- Vue3 官方源码:https://github.com/vuejs/core
- Vue3 diff算法:packages/runtime-core/src/renderer.ts
![打卡信奥刷题(3676)用C++实现信奥题 P12271 [蓝桥杯 2024 国 Python B] 括号与字母-171主机测评](https://www.171host.com/wp-content/uploads/2026/11/20261107050125-6aeeb0a5a5ade-220x150.png)

