vue.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > vue.js > vue 更新patch

vue页面更新patch的实现示例

作者:阿正

这篇文章主要介绍了vue页面更新patch的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

patch的流程

组件页面渲染时,将render返回的新vnode(新节点)和组件实例保存的vnode(旧节点)作为参数,调用patch方法,更新DOM。

判断两个节点是否相同

处理过程中,需要判断节点是否相同。相同节点需要满足以下条件:

function sameVnode (a, b) {// 判断两个VNode节点是否是同一个节点
  return (
    a.key === b.key && // key相同
    (
      a.tag === b.tag && // tag相同
      a.isComment === b.isComment && // 注释节点标识相同
      isDef(a.data) === isDef(b.data) && // data值状态相同
      sameInputType(a, b) // input的type相同
    )
  )
}

patch方法

patch判断流程如下:

a) 如果新节点为空,此时旧节点存在(组件销毁时),调用旧节点destroy生命周期函数

b) 如果旧节点为空,根据新节点创建DOM

c) 其他(如果新旧节点都存在)

function patch (oldVnode, vnode, hydrating, removeOnly) {
  if (isUndef(vnode)) {
   if (isDef(oldVnode)) { invokeDestroyHook(oldVnode); }
   return
  }
  ...
  if (isUndef(oldVnode)) {
   isInitialPatch = true;// 组件初始加载
   createElm(vnode, insertedVnodeQueue);
  } else {
   var isRealElement = isDef(oldVnode.nodeType);
   if (!isRealElement && sameVnode(oldVnode, vnode)) {
    patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly);
   } else {
    ...
    var oldElm = oldVnode.elm;
    var parentElm = nodeOps.parentNode(oldElm);// 获取父元素
    // create new node
    createElm(
     vnode,
     insertedVnodeQueue,
     oldElm._leaveCb ? null : parentElm,
     nodeOps.nextSibling(oldElm)// 获取紧跟的弟弟元素
    );
    if (isDef(parentElm)) {
     removeVnodes(parentElm, [oldVnode], 0, 0);// 销毁旧节点以及DOM元素
    } else if (isDef(oldVnode.tag)) {
     invokeDestroyHook(oldVnode);
    }
   }
  }
  invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
  return vnode.elm
 }
}

patchVnode方法

当两个节点相同时,执行patchVnode方法。在处理各种情况之前,会将旧节点elm属性值赋值给新节点的elm属性,保持elm保持一致。

具体流程如下:

a)如果新旧节点完全相同(引用相同 oldVnode === vnode)

b) 如果新节点不是文本节点

 function patchVnode (
  oldVnode,
  vnode,
  insertedVnodeQueue,
  ownerArray,
  index,
  removeOnly
 ) {
  if (oldVnode === vnode) {
   return
  }
  ...
  if (isUndef(vnode.text)) {
   if (isDef(oldCh) && isDef(ch)) {
    if (oldCh !== ch) { updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly); }
   } else if (isDef(ch)) {
    if (process.env.NODE_ENV !== 'production') {
     checkDuplicateKeys(ch);
    }
    if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, ''); }
    addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
   } else if (isDef(oldCh)) {
    removeVnodes(elm, oldCh, 0, oldCh.length - 1);
   } else if (isDef(oldVnode.text)) {
    nodeOps.setTextContent(elm, '');
   }
  } else if (oldVnode.text !== vnode.text) {
   nodeOps.setTextContent(elm, vnode.text);
  }
  ...
 }

updateChildren方法

updateChildren方法处理相同新旧节点的子节点。方法定义了以下变量(updateChildren的节点都表示的是子节点):

  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, // 尚未处理的旧节点key值映射
    idxInOld, // 与新节点key值相同的旧节点序号
    vnodeToMove, // 与新节点key值相同的旧节点
    refElm;// 指向当前正在处理的新结尾节点的后一个节点(已处理)的DOM元素

根据新旧节点的对比结果,更新DOM元素,此过程并不改变新旧节点的排序。序号指向正在处理的节点,分别是新旧节点的起始和结尾节点。对比过程以新起始节点为主导,对比方向是由两侧向中间。优先比对新旧节点的起始节点和结尾节点,再查找与新起始节点相同的且未处理的旧节点。当旧节点全部处理完(旧起始和结尾序号重叠),此时新节点可能未处理完,就添加新节点DOM元素。当新节点全部处理完(新起始和结尾序号重叠),可能存在旧节点,就删除旧节点DOM元素。

具体流程如下:

新旧子节点的起始序号不大于结尾序号时,执行以下流程:

a)如果旧子节点两侧存在undefined节点

b)新旧子节点的起始节点相同(前后比较)

c)新旧子节点的结尾节点相同(前后比较)

d)旧起始节点和新结尾节点相同(前后比较)

e)旧结尾节点和新起始节点相同(前后比较)

f)其他(缓存尚未处理的旧节点key值,依此判断旧节点中是否存在和新起始节点相同的节点)

循环结束

a)如果旧节点遍历完(oldStartIdx > oldEndIdx

b)如果新节点遍历完(newStartIdx > newEndIdx

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)) {
    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); }// 缓存尚未处理的旧节点key值
    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(parentElm, oldCh, oldStartIdx, oldEndIdx);
  }
 }

updateChildren的示例:

1.左边表示新旧节点,节点下面标识起始和结尾节点(即正在处理的节点)。右边表示当前的DOM。

2.新节点的起始和结尾节点与旧节点的起始和结尾节点互不相同,并且在旧节点中未找到与新起始节点(新节点f)相同的节点。
所以创建节点f的DOM并添加到旧起始节点(旧节点a)DOM的前面,然后新起始节点序号加1,表示新节点f已处理,当前正在处理新起始节点c。

3.新节点的起始和结尾节点与旧节点的起始和结尾节点互不相同,但在旧节点中找到与新起始节点(节点c)相同的节点。
所以将旧节点c的DOM添加到旧起始节点(旧节点a)DOM的前面,旧节点c置空,然后新起始节点序号加1,表示新节点c已处理,当前正在处理新起始节点e。

4.新起始节点(新节点e)和旧结尾节点(旧节点e)相同。更新旧节点e的DOM内容,并将旧节点e的DOM移动到旧起始节点(旧节点a)DOM的前面,旧结尾节点序号减1,新起始节点加1,表示新旧节点e已处理,当前正在处理的是新起始节点g和旧结尾节点d。

5.新结尾节点(新节点d)和旧结尾节点(旧节点d)相同。仅更新旧节点d的DOM内容。新结尾节点序号减1,旧结尾节点序号减1,表示新旧节点d已处理,当前正在处理的是新结尾节点g和旧结尾节点c。由于旧节点c为空,则旧结尾节点为b。

6.新节点的起始和结尾节点与旧节点的起始和结尾节点互不相同,并且在旧节点中未找到与新起始节点(新节点g)相同的节点。
所以创建节点g的DOM并添加到旧起始节点(旧节点a)DOM的前面,然后新起始节点序号加1,表示新节点g已处理,当前正在处理新起始节点d。

7.由于新起始和结尾节点序号重叠,新节点已经处理完毕,存在尚未处理的旧节点,则移除未处理的旧节点DOM。

8.结束,最终的DOM。

到此这篇关于vue页面更新patch的实现示例的文章就介绍到这了,更多相关vue 更新patch内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文