Vue 3 的 DOM diff 算法
Vue 3 的 DOM diff 算法(称为 快速 Diff 算法)相比 Vue 2 有了显著优化,主要特点是通过更高效的比较策略减少不必要的 DOM 操作。
1. 核心 Diff 流程
1.1 整体流程
function patchChildren(n1, n2, container) {
// 1. 预处理:处理文本、空节点等简单情况
// 2. 核心 Diff:比较新旧子节点数组
// 3. 更新 DOM
}
2. 快速 Diff 算法步骤
2.1 预处理阶段
function patchKeyedChildren(c1, c2, container) {
let i = 0
const l2 = c2.length
let e1 = c1.length - 1
let e2 = l2 - 1
// 1. 从前面开始同步
while (i <= e1 && i <= e2) {
const n1 = c1[i]
const n2 = c2[i]
if (isSameVNodeType(n1, n2)) {
patch(n1, n2, container) // 更新节点
} else {
break
}
i++
}
// 2. 从后面开始同步
while (i <= e1 && i <= e2) {
const n1 = c1[e1]
const n2 = c2[e2]
if (isSameVNodeType(n1, n2)) {
patch(n1, n2, container)
} else {
break
}
e1--
e2--
}
// 3. 处理新增/删除情况
if (i > e1) {
// 新增节点
if (i <= e2) {
const nextPos = e2 + 1
const anchor = nextPos < l2 ? c2[nextPos].el : null
while (i <= e2) {
patch(null, c2[i], container, anchor)
i++
}
}
} else if (i > e2) {
// 删除节点
while (i <= e1) {
unmount(c1[i])
i++
}
} else {
// 4. 复杂情况:乱序节点比较
const s1 = i
const s2 = i
const keyToNewIndexMap = new Map()
// 建立新节点 key 到 index 的映射
for (let i = s2; i <= e2; i++) {
keyToNewIndexMap.set(c2[i].key, i)
}
// 遍历旧节点,找出可复用的节点 【新节点在旧节点的位置,后面可以直接取】
const toBePatched = e2 - s2 + 1
const newIndexToOldIndexMap = new Array(toBePatched).fill(0)
for (let i = s1; i <= e1; i++) {
const prevChild = c1[i]
let newIndex
if (prevChild.key != null) {
newIndex = keyToNewIndexMap.get(prevChild.key)
} else {
// 没有 key 的情况,需要遍历查找
for (let j = s2; j <= e2; j++) {
if (isSameVNodeType(prevChild, c2[j])) {
newIndex = j
break
}
}
}
if (newIndex === undefined) {
// 新节点中没有,卸载
unmount(prevChild)
} else {
newIndexToOldIndexMap[newIndex - s2] = i + 1
patch(prevChild, c2[newIndex], container)
}
}
// 5. 移动和挂载新节点
const increasingNewIndexSequence = getSequence(newIndexToOldIndexMap)
let j = increasingNewIndexSequence.length - 1
for (let i = toBePatched - 1; i >= 0; i--) {
const nextIndex = s2 + i
const nextChild = c2[nextIndex]
const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : null
if (newIndexToOldIndexMap[i] === 0) {
// 新增节点
patch(null, nextChild, container, anchor)
} else {
if (i !== increasingNewIndexSequence[j]) {
// 需要移动
insert(nextChild.el, container, anchor)
} else {
j--
}
}
}
}
}
3. 具体示例分析
3.1 简单例子:前后同步
// 旧节点: [A, B, C, D]
// 新节点: [A, B, E, D]
// 步骤:
// 1. 前同步:比较 A-A, B-B ✓
// 2. 后同步:比较 D-D ✓
// 3. 中间部分:C -> E,替换操作
3.2 复杂例子:乱序移动
// 旧节点: [A, B, C, D, E, F]
// 新节点: [A, E, C, B, D, F]
// 步骤:
// 1. 前同步:A-A ✓
// 2. 后同步:F-F ✓
// 3. 中间部分 [B, C, D, E] vs [E, C, B, D] 需要复杂 Diff
// 建立映射:
// keyToNewIndexMap: {E:1, C:2, B:3, D:4} // 新节点映射表
// newIndexToOldIndexMap: [4, 2, 1, 3] // 新节点在旧节点位置
// 最长递增子序列: [2, 3] → [C, D]
// 移动操作:
// - B 需要移动到 D 前面
// - C, D 保持不动
4. 最长递增子序列算法
// 获取最长递增子序列
function getSequence(arr) {
const p = arr.slice()
const result = [0]
let i, j, u, v, c
const len = arr.length
for (i = 0; i < len; i++) {
const arrI = arr[i]
if (arrI !== 0) {
j = result[result.length - 1]
if (arr[j] < arrI) {
p[i] = j
result.push(i)
continue
}
u = 0
v = result.length - 1
while (u < v) {
c = (u + v) >> 1
if (arr[result[c]] < arrI) {
u = c + 1
} else {
v = c
}
}
if (arrI < arr[result[u]]) {
if (u > 0) {
p[i] = result[u - 1]
}
result[u] = i
}
}
}
u = result.length
v = result[u - 1]
while (u-- > 0) {
result[u] = v
v = p[v]
}
return result
}
5. 性能优化策略
5.1 静态提升
// 静态节点在编译阶段被提升,不参与 Diff
const hoisted = createVNode('div', null, 'static content')
function render() {
return createVNode('div', null, [
hoisted, // 静态节点,直接复用
createVNode('p', null, state.dynamicContent) // 动态节点
])
}
5.2 Patch Flags
// 编译时标记节点更新类型
const vnode = {
type: 'div',
patchFlag: 1, // TEXT - 只需要更新文本内容
children: dynamicText
}
// Patch Flags 类型:
// 1: TEXT - 动态文本
// 2: CLASS - 动态 class
// 4: STYLE - 动态 style
// 8: PROPS - 动态属性
// 16: FULL_PROPS - 需要完整 Diff
5.3 Block Tree
// 区块树,减少动态节点比较范围
function render() {
return openBlock(), createBlock('div', null, [
createVNode('p', null, '静态节点'), // 不参与 Diff
createVNode('p', null, state.dynamic, 1) // 只有这个参与 Diff
])
}
6. 与 Vue 2 Diff 的对比
| 特性 | Vue 2 | Vue 3 |
|---|---|---|
| 算法 | 双端 Diff | 快速 Diff + 最长递增子序列 |
| 时间复杂度 | O(n) | O(n) |
| 实际性能 | 较多 DOM 移动 | 最少 DOM 移动 |
| 静态优化 | 有限 | 静态提升、Patch Flags |
| 编译时优化 | 较少 | Block Tree、Tree Flattening |
7. 实际应用示例
// 列表渲染优化
const List = {
setup() {
const items = ref([{id: 1, name: 'A'}, {id: 2, name: 'B'}, {id: 3, name: 'C'}])
const reverse = () => {
items.value = items.value.reverse()
}
return { items, reverse }
},
render() {
return [
h('button', { onClick: this.reverse }, '反转'),
h('ul', null,
this.items.map(item =>
h('li', { key: item.id }, item.name)
)
)
]
}
}
总结
Vue 3 的 DOM Diff 主要优化点:
- 预处理:前后同步比较跳过相同前缀后缀
- 处理仅新增和仅删除
- Key 映射:建立 key-index 映射快速查找 【新节点映射表,新节点在旧节点位置】
- 最长递增子序列:找出最少移动的节点序列
- 编译时优化:静态提升、Patch Flags 减少运行时比较
- Block Tree:缩小 Diff 范围
这些优化使得 Vue 3 在复杂列表更新场景下性能显著提升,特别是在节点移动频繁的情况下。
挣钱养家

浙公网安备 33010602011771号