背景
研究一下Vue3 diff 源码,逐条加了注释,清晰明白,
source code patchKeyedChildren
/*
Vue3 diff 算法的核心
*/
const patchKeyedChildren = (
c1: VNode[],
c2: VNodeArrayChildren,
container: RendererElement,
parentAnchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean
) => {
let i = 0
const l2 = c2.length
/* 旧节点的尾部索引 */
let e1 = c1.length - 1 // prev ending index
/* 新节点的尾部索引 */
let e2 = l2 - 1 // next ending index
/* 从头部开始同步 */
// 1. sync from start
// (a b) c
// (a b) d e
while (i <= e1 && i <= e2) {
const n1 = c1[i]
const n2 = (c2[i] = optimized
? cloneIfMounted(c2[i] as VNode)
: normalizeVNode(c2[i]))
if (isSameVNodeType(n1, n2)) {
/* 相同的节点, 递归执行patch 更新节点 */
patch(
n1,
n2,
container,
null,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
} else {
/* 结束while循环 */
break
}
i++
}
/* 从尾部开始同步尾部节点 */
// 2. sync from end
// a (b c)
// d e (b c)
while (i <= e1 && i <= e2) {
const n1 = c1[e1]
const n2 = (c2[e2] = optimized
? cloneIfMounted(c2[e2] as VNode)
: normalizeVNode(c2[e2]))
if (isSameVNodeType(n1, n2)) {
/* 如果是相同的节点,递归执行patch,更新节点 */
patch(
n1,
n2,
container,
null,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
} else {
/* 结束while循环 */
break
}
e1--
e2--
}
// 3. common sequence + mount
// (a b)
// (a b) c
// i = 2, e1 = 1, e2 = 2
// (a b)
// c (a b)
// i = 0, e1 = -1, e2 = 0
/* 新子节点 有剩余要添加的新节点 */
if (i > e1) {
if (i <= e2) {
const nextPos = e2 + 1
const anchor = nextPos < l2 ? (c2[nextPos] as VNode).el : parentAnchor
while (i <= e2) {
patch(
null,
(c2[i] = optimized
? cloneIfMounted(c2[i] as VNode)
: normalizeVNode(c2[i])),
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
i++
}
}
}
// 4. common sequence + unmount
// (a b) c
// (a b)
// i = 2, e1 = 2, e2 = 1
// a (b c)
// (b c)
// i = 0, e1 = 0, e2 = -1
/* 4. 旧子节点,有剩余要删除的多余节点 */
else if (i > e2) {
while (i <= e1) {
/* 删除多余的节点 */
unmount(c1[i], parentComponent, parentSuspense, true)
i++
}
}
// 5. unknown sequence
// [i ... e1 + 1]: a b [c d e] f g
// [i ... e2 + 1]: a b [e d c h] f g
// i = 2, e1 = 4, e2 = 5
/* 未知的子序列 这里 更加复杂 */
else {
/* 旧子序列开始索引,从 i 开始记录 */
const s1 = i // prev starting index
/* 新子序列开始索引,从 i 开始记录 */
const s2 = i // next starting index
/*
5.1 build key:index map for newChildren
根据key 建立 新子序列的索引图
*/
const keyToNewIndexMap: Map<string | number | symbol, number> = new Map()
for (i = s2; i <= e2; i++) {
const nextChild = (c2[i] = optimized
? cloneIfMounted(c2[i] as VNode)
: normalizeVNode(c2[i]))
if (nextChild.key != null) {
if (__DEV__ && keyToNewIndexMap.has(nextChild.key)) {
warn(
`Duplicate keys found during update:`,
JSON.stringify(nextChild.key),
`Make sure keys are unique.`
)
}
/* key 对应 i */
keyToNewIndexMap.set(nextChild.key, i)
}
}
/*
5.2 循环遍历 旧子序列 patch
*/
let j
let patched = 0
const toBePatched = e2 - s2 + 1
let moved = false
/* 用于跟踪判断是否有节点移动 */
let maxNewIndexSoFar = 0
/* 这个数组用于存储 新子序中的元素在旧子序列节点的索引,用于确定,最长递增子序列 */
const newIndexToOldIndexMap = new Array(toBePatched)
/* 初始化数组,每个元素都是0, 0 是一个特殊的值,如果遍历完了,仍然有元素的值为0,
则说明这个新节点没有对应的旧节点 */
for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0
/* 循环遍历 旧子节点 */
for (i = s1; i <= e1; i++) {
/* 每一个旧子序列节点 */
const prevChild = c1[i]
/* toBePatched 代表新子序列的长度 */
if (patched >= toBePatched) {
/* 所有的新的子序列的节点都已经更新,剩余的旧子序列的节点删除 */
unmount(prevChild, parentComponent, parentSuspense, true)
continue
}
let newIndex
if (prevChild.key != null) {
/* 查找旧子序列中的节点在新子序列中的索引 */
newIndex = keyToNewIndexMap.get(prevChild.key)
} else {
// key-less node, try to locate a key-less node of the same type
for (j = s2; j <= e2; j++) {
if (
newIndexToOldIndexMap[j - s2] === 0 &&
isSameVNodeType(prevChild, c2[j] as VNode)
) {
newIndex = j
break
}
}
}
/* 如果没找到, 说明旧子序列中的节点不存在新子序列中,那么就删除旧子序列的这个节点 */
if (newIndex === undefined) {
unmount(prevChild, parentComponent, parentSuspense, true)
} else {
/* 更新,新子序列的元素 在旧子序列中的索引
这里加上1 偏移, 是为了避免i为0的特殊情况,影响对后续 最长递增子序列的求解
*/
newIndexToOldIndexMap[newIndex - s2] = i + 1
/* maxNewIndexSoFar 始终存储的是上次求值的newIndex,如果不是一直递增,则说明有移动 */
if (newIndex >= maxNewIndexSoFar) {
maxNewIndexSoFar = newIndex
} else {
moved = true
}
/* 更新新旧子序列中匹配的节点 */
patch(
prevChild,
c2[newIndex] as VNode,
container,
null,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
patched++
}
} /* 循环遍历旧节点 END */
// 5.3 move and mount
/*
moved 为TRUE 说明有移动
getSequence 计算最长递增子序列, 这是最复杂的算法
newIndexToOldIndexMap: [5, 3, 4, 0] 里面存的值 是旧的子序列的索引
5 要移动到4的后面,0占位,需要新增一个节点
假如 newIndexToOldIndexMap 为 [5, 3, 4, 0] 那么 最长递增子序列就是 [1,2] 里面存的是索引
*/
const increasingNewIndexSequence = moved
? getSequence(newIndexToOldIndexMap)
: EMPTY_ARR
j = increasingNewIndexSequence.length - 1
/*
使用倒序的方式,遍历 toBePatched 是新序列中的要对比的部分的长度
方便我们使用最后更新的节点作为锚点
5.2是遍历了旧的,这一次要遍历新的
*/
for (i = toBePatched - 1; i >= 0; i--) {
const nextIndex = s2 + i
const nextChild = c2[nextIndex] as VNode
/* 锚点指向上一个更新的节点,如果 nextIndex 超过新子节点的长度,则指向parentAnchor */
const anchor =
nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor
if (newIndexToOldIndexMap[i] === 0) {
/* 0 是占位,mount 挂载新节点 */
patch(
null,
nextChild,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
} else if (moved) {
/*
没有最长递增子序列(reverse的场景)或者当前的节点索引不在最长递增子序列中,需要移动
移动到上一次操作节点的前面
*/
if (j < 0 || i !== increasingNewIndexSequence[j]) {
move(nextChild, container, anchor, MoveType.REORDER)
} else {
j--
}
}
}
}
} /* patchKeyedChildren end */
getSequence
计算最长递增子序列, 里面最复杂的算法
/* 计算最长递增子序列
贪心+ 二分查找
贪心是O(n)
二分查找是O(logn)
总的是 时间复杂度 O(logn)
*/
// https://en.wikipedia.org/wiki/Longest_increasing_subsequence
function getSequence(arr: number[]): number[] {
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
}