You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
returnfunctionpatch(oldVnode,vnode,hydrating,removeOnly,parentElm,refElm){
...
if(isUndef(oldVnode)){// empty mount (likely as component), create new root elementisInitialPatch=true;createElm(vnode,insertedVnodeQueue,parentElm,refElm);}else{// 我们上一次的oldVnode 是Virtual DOM 所以isRealElement为false varisRealElement=isDef(oldVnode.nodeType);if(!isRealElement&&sameVnode(oldVnode,vnode)){patchVnode(oldVnode,vnode,insertedVnodeQueue,removeOnly);}}}
functionupdateChildren(parentElm,oldCh,newCh,insertedVnodeQueue,removeOnly){varoldStartIdx=0;varnewStartIdx=0;varoldEndIdx=oldCh.length-1;varoldStartVnode=oldCh[0];varoldEndVnode=oldCh[oldEndIdx];varnewEndIdx=newCh.length-1;varnewStartVnode=newCh[0];varnewEndVnode=newCh[newEndIdx];varoldKeyToIdx,idxInOld,vnodeToMove,refElm;// removeOnly is a special flag used only by <transition-group>// to ensure removed elements stay in correct relative positions// during leaving transitionsvarcanMove=!removeOnly;{checkDuplicateKeys(newCh);}while(oldStartIdx<=oldEndIdx&&newStartIdx<=newEndIdx){if(isUndef(oldStartVnode)){oldStartVnode=oldCh[++oldStartIdx];// Vnode has been moved left}elseif(isUndef(oldEndVnode)){oldEndVnode=oldCh[--oldEndIdx];}elseif(sameVnode(oldStartVnode,newStartVnode)){patchVnode(oldStartVnode,newStartVnode,insertedVnodeQueue);oldStartVnode=oldCh[++oldStartIdx];newStartVnode=newCh[++newStartIdx];}elseif(sameVnode(oldEndVnode,newEndVnode)){patchVnode(oldEndVnode,newEndVnode,insertedVnodeQueue);oldEndVnode=oldCh[--oldEndIdx];newEndVnode=newCh[--newEndIdx];}elseif(sameVnode(oldStartVnode,newEndVnode)){// Vnode moved rightpatchVnode(oldStartVnode,newEndVnode,insertedVnodeQueue);// 注意这里涉及到节点移动canMove&&nodeOps.insertBefore(parentElm,oldStartVnode.elm,nodeOps.nextSibling(oldEndVnode.elm));oldStartVnode=oldCh[++oldStartIdx];newEndVnode=newCh[--newEndIdx];}elseif(sameVnode(oldEndVnode,newStartVnode)){// Vnode moved leftpatchVnode(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)){// New elementcreateElm(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 elementcreateElm(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);}elseif(newStartIdx>newEndIdx){removeVnodes(parentElm,oldCh,oldStartIdx,oldEndIdx);}}
}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 elementcreateElm(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 elementcreateElm(newStartVnode,insertedVnodeQueue,parentElm,oldStartVnode.elm,false,newCh,newStartIdx);}}newStartVnode=newCh[++newStartIdx];}
patch 方法骨架
patchVnode 方法
updateChildren 方法
看一下大的结构,分为以下几个维度比较:
比较oldStartVnode 与 newStartVnode
比较oldEndVnode 与 newEndVnode
比较oldStartVnode 与 newEndVnode
比较oldEndVnode与newStartVnode
上面4中情况都不符合时,单独讲
依次判断他们是否为sameVnode,如果是,则再进入patchVnode方法
下面我们结合实际的例子,假设我们的模板是下面的样子, 原先data是:
前面的4种情况挺容易让人理解的,就是从根节点开始进入patchVnode(oldVnode, vnode),若根节点有children进入updateChildren方法,updateChildren里面定义了新老vnode 树的索引: oldStartIdx, oldEndIdx, newStartIdx, newEndIdx。然后进行4种维度的两两对比。当oldStart与newEnd一致时,会更新oldStart 同时将这个节点移动到oldEnd后面位置; 同理当oldEnd与newStart一致时也会更新并对节点进行移动;如果oldStart 与 newStart一致,直接更新节点内容;如果oldEnd与newEnd一致同理直接更新节点内容。
如果这4种情况都不满足,怎么处理?会进入下面的阶段, 下面这个阶段大部分会进入createElm 这个方法,那什么时候会进入createKeyToOldIdx呢?让我们大致看下这个方法
我们来看下createKeyToOldIdx 方法, 大致从这个方法可以看出和
:key='xx'
这种相关,这个一般在li中vue会给我们建议设置key, 那这个好处到底在于哪里呢?举个例子,如果我们不绑key, 如果我们遍历items=[1,2,3,4,5]; 后面更新数据为[1,2,6,3,4,5] 那么dom更新的过程,按照上面的分析必然为下图所示:
而当我们设置了key时,因为进入sameVnode判断的时候会判断key, 所以我们的比较会变成,下图所示,1,2都是同级比较,然后到3的时候会满足oldEnd与newEnd一致,所以开始进入5的比较,依次类推,所以dom都被复用了,最后只要在对应位置插入6就好了
其实也就是这张经典的图
参考资料
1.vue2.0 virtual-dom实现简析 DDFE/DDFE-blog#18
2.vue2.0中 v-for的key 到底有什么用? https://www.zhihu.com/question/61064119
3.VirtualDOM与diff(Vue实现) https://github.com/answershuto/learnVue/blob/master/docs/VirtualDOM%E4%B8%8Ediff(Vue%E5%AE%9E%E7%8E%B0).MarkDown
The text was updated successfully, but these errors were encountered: