重点
newIndexToOldIndexMap(下标为新节点索引,值为旧节点索引+1)
const old = (a) b c d (e)
const new = (a) c d b (e)
调用栈大概如下patchElement('divWrapper')->patchChildren->patchKeyedChildren
,重点就是patchKeyedChildren
// 第一个while循环
while (i <= e1 && i <= e2)
如果的元素是相同的(key和type都相同),则只会patchElement(c1,c2)
,并且继续对比下一个,如果遇到不是相同的元素,就break,这时也知道了对比了多少个相同的元素了。
// 第二个while循环
while (i <= e1 && i <= e2)
如果的元素是相同的(key和type都相同),则只会patchElement(c1,c2)
,并且继续对比前一个,如果遇到不是相同的元素,就break,这时也知道了对比了多少个相同的元素了。
字母是
key
,数字是所在children数组的下标
接下来发现在 old 中还剩b:1 c:2 d:3
, new 中还剩c:1 d:2 b:3
,这也是diff的关键所在,首先我们记录在新的children中,各个key对应的下标,既keyToNewIndexMap
。
然后要处理剩余的old
,在新的剩余项中keyToNewIndexMap
,如果有对应的key,就patch(prevChild, c2),否则就unmount(prevChild),如果存在vue不仅要patch,还要处理位置是否移动,vue通过最长子序列来实现。所以我们需要知道在新数组中的元素他们原来在旧数组中所在的位置。
// 为什么 newIndex - s2 这表示在剩余新增元素组成数组中的位置(下标)
// 这个+1 是一个特殊处理,因为vue 内部0 表示这个元素需要挂载
newIndexToOldIndexMap[newIndex - s2] = i + 1
// 这里就算是加10也无所谓,重点是趋势
// 我在fork的仓库中尝试了+10,单元测试是完全通过的
旧元素剩余数组b:1 c:2 d:3
b:1
,在新剩余数组中的下标是2
= 3(新数组中的下标)- s2
,newIndexToOldIndexMap中的b元素的值就是2
=1(旧完整数组所在的位置,也就是冒号后面的数字) + 1
c:2
,在新剩余数组中的下标是0
=1(新数组中的下标)- s2
,newIndexToOldIndexMap中的c元素的值就是3
=2(旧完整数组所在的位置,也就是冒号后面的数字) + 1
d:3
,在新剩余数组中的下标是1
=2(新数组中的下标)- s2
,newIndexToOldIndexMap中的d元素的值就是4
=3(旧完整数组所在的位置,也就是冒号后面的数字) + 1
得到最新递增子序列[3,4]也就是c,d
,那么就表示c,d
不需要移动。
最后在遍历新的
剩余元素组成的数组,如果元素不在最长递增子序列中,并且又不是新增元素,那么就移动位置。
const patchKeyedChildren = (
c1: VNode[],
c2: VNodeArrayChildren,
container: RendererElement,
parentAnchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
namespace: ElementNamespace,
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(
n1,
n2,
container,
null,
parentComponent,
parentSuspense,
namespace,
slotScopeIds,
optimized
)
} else {
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(
n1,
n2,
container,
null,
parentComponent,
parentSuspense,
namespace,
slotScopeIds,
optimized
)
} else {
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,
namespace,
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
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 {
const s1 = i // prev starting index
const s2 = i // next starting index
// 5.1 build key:index map for newChildren
const keyToNewIndexMap: Map<PropertyKey, 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.`
)
}
keyToNewIndexMap.set(nextChild.key, i)
}
}
// 5.2 loop through old children left to be patched and try to patch
// matching nodes & remove nodes that are no longer present
let j
let patched = 0
// 新数组剩余的个数
const toBePatched = e2 - s2 + 1
let moved = false
// used to track whether any node has moved
let maxNewIndexSoFar = 0
// works as Map<newIndex, oldIndex>
// Note that oldIndex is offset by +1
// and oldIndex = 0 is a special value indicating the new node has
// no corresponding old node.
// used for determining longest stable subsequence
const newIndexToOldIndexMap = new Array(toBePatched)
for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0
for (i = s1; i <= e1; i++) {
const prevChild = c1[i]
if (patched >= toBePatched) {
// all new children have been patched so this can only be a removal
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 {
// 建立旧child和新child的位置映射
// newIndexToOldIndexMap[下标] = 旧数组中的位置
// 下标就是0,1,2等就是新数组的 child
// 比如 [0:c,1:d,2:b]
newIndexToOldIndexMap[newIndex - s2] = i + 10
if (newIndex >= maxNewIndexSoFar) {
maxNewIndexSoFar = newIndex
} else {
moved = true
}
patch(
prevChild,
c2[newIndex] as VNode,
container,
null,
parentComponent,
parentSuspense,
namespace,
slotScopeIds,
optimized
)
patched++
}
}
// 5.3 move and mount
// generate longest stable subsequence only when nodes have moved
const increasingNewIndexSequence = moved
? getSequence(newIndexToOldIndexMap)
: EMPTY_ARR
j = increasingNewIndexSequence.length - 1
// looping backwards so that we can use last patched node as anchor
// 处理新数组中是否移动和挂载
for (i = toBePatched - 1; i >= 0; i--) {
const nextIndex = s2 + i
const nextChild = c2[nextIndex] as VNode
const anchor =
nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor
if (newIndexToOldIndexMap[i] === 0) {
// mount new
patch(
null,
nextChild,
container,
anchor,
parentComponent,
parentSuspense,
namespace,
slotScopeIds,
optimized
)
} else if (moved) {
// move if:
// There is no stable subsequence (e.g. a reverse)
// OR current node is not among the stable sequence
if (j < 0 || i !== increasingNewIndexSequence[j]) {
// 如果new child 不在 最长递增子序列中,就移动
move(nextChild, container, anchor, MoveType.REORDER)
} else {
j--
}
}
}
}
}