vue虚拟DOM核心------patch

402 阅读1分钟

1.什么叫patch?简单了解patch做了什么?

据上文了解,我们为了节省频繁操作DOM,建立了一个vnode虚拟节点,根据新旧节点的差异,我们才去操作真实的DOM。而关于比较的这个过程,我们就叫patch。

var patch = createPatchFunction();
Vue.prototype.__patch__ =  patch

patch做了什么?

  • 创建新增的节点(元素节点,注释节点,文本节点)
  • 删除已经不存在的节点
  • 修改需要更新的节点

2.patch主要流程

1616507143(1).png

3.新增节点

if (isUndef(oldVnode)) { //如果oldValue为空
      isInitialPatch = true;
      createElm(vnode, insertedVnodeQueue, parentElm, refElm);
    }
对于三种节点的区分:元素节点必定有tag,注释节点isComment必定为true,如果两者皆不满足,那么就是文本节点
if (isDef(tag)) {}
else if (isTrue(vnode.isComment)){}
else{}

4.删除节点

function removeVnodes (vnodes, startIdx, endIdx) {
    for (; startIdx <= endIdx; ++startIdx) {
      var ch = vnodes[startIdx];
      if (isDef(ch)) {
        if (isDef(ch.tag)) {
          removeAndInvokeRemoveHook(ch); //移除DOM并执行remove钩子函数
          invokeDestroyHook(ch); //执行 module 的 destory 钩子函数以及 vnode 的 destory 钩子函数
        } else { // Text node
          removeNode(ch.elm);
        }
      }
    }
  }

5.更新节点

1.如果是静态节点会直接跳过更新
2.如果新节点具有text属性且不等于旧节点的text,那么不论之前的子节点是什么,直接调用setTextContent方法将DOM节点内容改成text属性保存的文字
3.判定是否有children,如果新节点无children。那么就直接删除旧节点的childern。如果有children,我们需要对children进行更加详细的对比,包括删除,新增。移动位置等

6.关于diff的实现策略 updateChildren方法

  • 新前: newChildren中所有未处理的第一个节点
  • 新后: newChildren中所有未处理的最后一个节点
  • 旧前: oldChildren中所有未处理的第一个节点
  • 旧后: oldChildren中所有未处理的最后一个节点

比较更新步骤

新前与旧前

sameVnode(oldStartVnode, newStartVnode)

1616510814(1).png

新尾与旧尾

sameVnode(oldStartVnode, newStartVnode)

1616510977(1).png

新尾与旧头

sameVnode(oldStartVnode, newEndVnode)

1616511107(1).png 此时如果节点相同就需要移动位置.源代码是:

nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm));
function insertBefore (parentNode, newNode, referenceNode) {
  parentNode.insertBefore(newNode, referenceNode);
}
insertBefore函数,如果当referenceNode为空时会插在当前父节点末尾

1616551784(1).png

新头与旧尾

sameVnode(oldEndVnode, newStartVnode) //比较
nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm) //插入

1616511235(1).png

创建索引,遍历查询

createKeyToOldIdx && findIdxInOld
遍历新节点是否能在旧节点列表中能否找到,如果不能找到,直接创建,如果能找到,
比较是否是同一个节点,如果是移动到oldStartVnode节点的前面.如果不同,
直接创建插入 oldStartVnode 前面

处理剩余的节点

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);
}

附上原代码

  function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
    var oldStartIdx = 0; 
    var newStartIdx = 0;
    var oldEndIdx = oldCh.length - 1;
    var oldStartVnode = oldCh[0];
    var oldEndVnode = oldCh[oldEndIdx];
    var newEndIdx = newCh.length - 1;
    var newStartVnode = newCh[0];
    var newEndVnode = newCh[newEndIdx];
    var oldKeyToIdx, idxInOld, vnodeToMove, refElm;

    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
      if (isUndef(oldStartVnode)) { //判定是否存在,是因为每次匹配到一个节点时,会将这个节点设置为undefined或者false
        oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left 会
      } else if (isUndef(oldEndVnode)) {
        oldEndVnode = oldCh[--oldEndIdx];
      } else if (sameVnode(oldStartVnode, newStartVnode)) { //旧头新头
        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);
        oldStartVnode = oldCh[++oldStartIdx];
        newStartVnode = newCh[++newStartIdx];
      } else if (sameVnode(oldEndVnode, newEndVnode)) { //旧尾与新尾
        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue);
        oldEndVnode = oldCh[--oldEndIdx];
        newEndVnode = newCh[--newEndIdx];
      } else if (sameVnode(oldStartVnode, newEndVnode)) { // 旧头与新尾
        patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);
        canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm));
        oldStartVnode = oldCh[++oldStartIdx];
        newEndVnode = newCh[--newEndIdx];
      } else if (sameVnode(oldEndVnode, newStartVnode)) { // 旧尾与新头
        patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);
        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)) { // 新节点无法在旧节点中找到
          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx);
        } else {
          vnodeToMove = oldCh[idxInOld];
          if (sameVnode(vnodeToMove, newStartVnode)) {
            patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue);
            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);
    }
  }