# 对 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)
    }
1
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

				并插入到现有的节点中去

			:

				创建新的节点插入进去
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 致谢

感谢大家阅读我的文章,如果对我感兴趣可以点击页面右上角,帮我点个star。

作者:前端小然子

链接: https://xiaoranzife.com/guide/vue/vue-dom-diff.html

来源:前端小然子的博客

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

上次更新: 2019-11-11 1:41:29 ├F10: PM┤