diff算法的流程
1、从头对比找到有相同的节点 patch ,发现不同,立即跳出。
2、如果第一步没有patch完,立即,从后往前开始patch ,如果发现不同立即跳出循环。
3、如果新的节点大于老的节点数 ,对于剩下的节点全部以新的vnode处理( 这种情况说明已经patch完相同的vnode )。
4、对于老的节点大于新的节点的情况 , 对于超出的节点全部卸载 ( 这种情况说明已经patch完相同的vnode )。
5、不确定的元素( 这种情况说明没有patch完相同的vnode ) 与 3 ,4对立关系。
把没有比较过的新的vnode节点,通过map保存
记录已经patch的新节点的数量 patched
没有经过 path 新的节点的数量 toBePatched
建立一个数组newIndexToOldIndexMap,每个子元素的初始值都是[ 0, 0, 0, 0, 0, 0, ] 里面的数字记录老节点的索引 ,数组索引就是新节点的索引, 数组索引是从0开始的
开始遍历老节点
① 如果 toBePatched新的节点数量为0 ,那么统一卸载老的节点
② 如果,老节点的key存在 ,通过key找到对应的index
③ 如果,老节点的key不存在
1 遍历剩下的所有新节点
2 如果找到与当前老节点对应的新节点,那么将新节点的索引,赋值给newIndex
④ 没有找到与老节点对应的新节点,卸载当前老节点。
⑤ 如果找到与老节点对应的新节点,把老节点的索引,记录在存放新节点的数组中,
1 如果节点发生移动 记录已经移动了
2 patch新老节点 找到新的节点进行patch节点
遍历结束
如果发生移动
① 根据 newIndexToOldIndexMap 新老节点索引列表找到最长稳定序列
② 对于 newIndexToOldIndexMap -item =0 证明不存在老节点 ,从新形成新的vnode
③ 对于发生移动的节点进行移动处理。
diff源码解析
js
// can be all-keyed or mixed
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 -> patchElememt
patch(
n1,
n2,
container,
null,
parentComponent,
parentSuspense,
isSVG,
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,
isSVG,
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,
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
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
// keyToNewIndexMap的key是新节点的key, value是新节点的位置/索引
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.`
)
}
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
// newIndexToOldIndexMap是一个用于建立新子节点在旧子节点列表中的索引映射的数组, 它的索引表示新子节点列表的索引, 而数组中的值表示该子节点在旧子节点列表中的索引
// 这个数组的创建是为了在后续的循环中构建`newIndexToOldIndexMap`,以便在对比和更新中间部分差异的时候,能够快速地找到新子节点在旧子节点列表中的对应节点。
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) { // 如果旧节点key存在
// keyToNewIndexMap对象中key是新节点的key,value是该key所在的位置
newIndex = keyToNewIndexMap.get(prevChild.key) // 通过相同key确认旧节点在新节点列表中的位置
} else {
//如果旧节点没有key
// 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 //通过isSameVNodeType确认旧节点在新节点列表中的位置
break
}
}
}
if (newIndex === undefined) {
// 在新节点列表中找不到旧节点,则删除该旧节点
unmount(prevChild, parentComponent, parentSuspense, true)
} else {
// i是节点在旧列表中的索引, newIndex是节点在新列表中的索引
// newIndexToOldIndexMap 是一个数组
// i在s1到e1之间, newIndex在s2到e2之间
// 如:[s1,...el] = [2,3,4,5,6] = [20,30,40,50,60]
// 如果[20,30,40,50,60]变成[2,3,4,5,6]=>[40,50,20,30,60]
// i=2; newIndex=4; s2=2 newIndexToOldIndexMap=[2] = 3
// i=3; newIndex=5 newIndexToOldIndexMap=[3] = 4
// i=4; newIndex=2 newIndexToOldIndexMap=[0] = 5
// i=5; newIndex=3 newIndexToOldIndexMap=[1] = 6
// i=6; newIndex=6 newIndexToOldIndexMap=[4] = 7
// newIndexToOldIndexMap = [5,6,3,4,7]
// 这里为什么是i + 1,是为了防止i=0的情况,当i=0时,如果不加1,newIndexToOldIndexMap这一项的值就为0,而newIndexToOldIndexMap的初始值就为0,代表新增的节点,
newIndexToOldIndexMap[newIndex - s2] = i + 1
if (newIndex >= maxNewIndexSoFar) {
maxNewIndexSoFar = newIndex
} else {
moved = true
}
// 对比新旧节点的差异,更新旧节点
patch(
prevChild,
c2[newIndex] as VNode,
container,
null,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
patched++
}
}
// 5.3 move and mount
// generate longest stable subsequence only when nodes have moved
// getSequence获取最长递增子序列
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,
isSVG,
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]) {
move(nextChild, container, anchor, MoveType.REORDER)
} else {
j--
}
}
}
}
}
js
const patchElement = (n1, n2, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized) => {
// 将旧节点的el(即真实DOM)赋值给 新节点的el
const el = n2.el = n1.el;
```
if (patchFlag & 1) {
if (n1.children !== n2.children) {
hostSetElementText(el, n2.children);
}
}
```
}
js
// 将新节点上的新的内容赋值给旧节点对应的真实DOM上;
// 此时新节点上的el和旧节点上的el是同一对象,
setElementText: (el, text) => {
el.textContent = text;
},
js
const move = (vnode, container, anchor, moveType, parentSuspense = null) => {
const { el, type, transition, children, shapeFlag } = vnode;
if(){
````
} else {
// 将50移动到20前面会走该分支
hostInsert(el, container, anchor);
}
};
js
// insertBefore 会完成节点的移动
// insertBefore方法在参考节点的前面插入一个拥有指定父节点的子节点,如果给定的子节点是对文档中现有节点的引用,insertBefore()会将其从当前位置移动到新位置
const nodeOps = {
insert: (child, parent, anchor) => {
parent.insertBefore(child, anchor || null);
},
}