# 对 Vue Dom Diff 的理解
# 核心源代码
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx]
} else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
} else {
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
if (isUndef(idxInOld)) { // New element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
} else {
vnodeToMove = oldCh[idxInOld]
if (sameVnode(vnodeToMove, newStartVnode)) {
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
oldCh[idxInOld] = undefined
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
} else {
// same key but different element. treat as new element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
}
}
newStartVnode = newCh[++newStartIdx]
}
}
if (oldStartIdx > oldEndIdx) {
refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
} else if (newStartIdx > newEndIdx) {
removeVnodes(oldCh, oldStartIdx, oldEndIdx)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
先来分析下这些判断,然后再举例说明
let oldStartIdx = 0
let newStartIdx = 0
let oldEndIdx = oldCh.length - 1
let newEndIdx = newCh.length - 1
StartIdx 是从 0 -> Max 递增
EndIdx 是从 Max -> 0 递减
这里遍历的一直是虚拟节点
patchVnode 或 nodeOps.insertBerfore 等 nodeOps 相关的会处理到真实 dom
# 开始循环 每一次都会经过
while:
首先,要先判断 oldStartVnode 是否存在
不存在则递增一个索引值,拿到下一个StartVnode,跳出循环
不存在则递减一个索引值,拿到下一个EndVnode,跳出循环
每一次进入 if 后都会跳出循环,后面不再说跳出循环了
# 第一次正式比较
比较 oldStartVnode VS newStartVnode 是否一致(旧的起始Vnode和新的起始Vnode)
如果一致,进入 if 比较并处理不一致的子节点(含文本节点)
旧的起始 Idx 和新的起始 Idx 都递增1
# 第二次正式比较
比较 oldEndVnode VS newEndVnode 是否一致
如果一致,进入 if 比较并处理不一致的子节点(含文本节点)
旧的结尾 Idx 和新的结尾 Idx 都递减1
# 第三次正式比较
比较 oldStartVnode VS newEndVnode 是否一致
如果一致,进入 if 比较并处理不一致的子节点(含文本节点)
子节点处理完毕了 canMove 可以移动了
将这个旧的节点插入到当前 newEndIdx 位置
旧的起始 Idx 递增1
新的结尾 Idx 递减1
# 第四次正式比较
比较 oldEndVnode VS newStartVnode 是否一致
如果一致,进入 if 比较并处理不一致的子节点(含文本节点)
子节点处理完毕了 canMove 可以移动了
将这个旧的节点插入到当前 newStartIdx 位置
旧的结束 Idx 递减1
新的起始 Idx 递增1
# 如果都不是
# 首先看一下是否存在当前未处理的oldIdx的key的集合/数组
不存在则创建
# 当前这个newStartVnode是否设置了key值
如果设置了则通过key在旧的Idx来查找对应的oldIdx 无则为undefined
如果没有设置 key 则通过 findIdxInOld 在当前未处理的oldVnodes中是否可以找到对应的节点 无则为 undefined
判断是否存在
**不存在**:创建一个新的Element并插入进去
**存在**:对比节点是否一致
?
则比较对应的子节点并处理,将原始的对应的oldVnode标记设置为undefined
并插入到现有的节点中去
:
创建新的节点插入进去
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 致谢
感谢大家阅读我的文章,如果对我感兴趣可以点击页面右上角,帮我点个star。
作者:前端小然子
链接: https://xiaoranzife.com/guide/vue/vue-dom-diff.html
来源:前端小然子的博客
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。