当前位置: 首页 > Web前端 > vue.js

极为细致的Vue的Diff流程详解——以流程图表达

时间:2023-03-31 23:38:02 vue.js

非常详细的Vue的Diff流程讲解——用流程图表达简单明了,说动就动。下面是基于vue2.6.11版本源码为读者分析的vue的diff流程图。层次比较,所以以相同层次为例。旧节点列表的起始节点和结束节点在下面称为OS(OldStarVnode)和OE(OldEndVnode),OS和OE在遍历过程中的变化用索引标示。即OS和OE的索引分别称为OSIndex和OEIndex。同理,新节点为NS和NE,NSIndex和NEIndex。遍历的新节点被创建并添加到节点列表中。如果新的节点列表已经遍历过,则将没有遍历过的旧节点从节点列表中删除。老节点的OS和OE会判断为空。如果为空,则跳过该节点,继续从1开始;否则继续3比较OS、OE、NS、NE。如果相等,则更新节点并将指针移动到下一个,继续从1开始;否则继续4判断NS是否有key。key是判断NS是否在旧节点列表中找到相同的key并更新;否则,创建NS并插入节点列表updateChildren执行diff算法源码函数updateChildren(parentElm,oldCh,newCh,insertedVnodeQueue,removeOnly){letoldStartIdx=0letnewStartIdx=0letoldEndIdx=oldCh.length-1letoldStartVnode=oldCh[0]letoldEndVnode=oldCh[oldEndIdx]letnewEndIdx=newCh.length-1letnewStartVnode=newCh[0]letnewEndVnode=newCh[newEndIdx]letoldKeyToIdx,idxInOld,vnodeToMove,refElm//removeOnly是一个只使用的特殊标志通过//以确保删除的元素保持在正确的相对位置//在离开转换期间constcanMove=!removeOnlyif(process.env.NODE_ENV!=='production'){checkDuplicateKeys(newCh)}while(oldStartIdx<=oldEndIdx&&newStartIdx<=newEndIdx){if(isUndef(oldStartVnode)){oldStartVnode=oldCh[++oldStartIdx]//Vnode已向左移动}elseif(isUndef(oldEndVnode)){oldEndVnode=oldCh[--oldEndIdx]}elseif(sameVnode(oldStartVnode,newStartVnode)){patchVnode(oldStartVnode,newStartVnode,insertedVnodeQueue,newCh,newStartIdx)oldStartVnode=oldCh[++oldStartIdx]newStartVnode=newCh[++newStartIdx]}elseif(sameVnode(oldEndVnode,newEndVnode)){patchVnode(oldEndVnode,newEndVnode,insertedVnodeQueue,newCh,newEndIdx)oldEndVnode=oldCh[--oldEndIdx]newEndVnode=newCh[--newEndVnode]}elseif(sameVnode(oldStartVnode,newEndVnode)){//Vnode右移patchVnode(oldStartVnode,newEndVnode,insertedVnodeQueue,newCh,newEndIdx)canMove&&nodeOps.insertBefore(pairstElm,oldStartVnode.elm,nodeOps.nextSibling(oldEndVnode.elm))oldStartVnode=oldCh[++oldStartIdx]newEndVnode=newCh[--newEndIdx]}elseif(sameVnode(oldEndVnode,newStartVnode)){//Vnode向左移动patchVnode(oldEndVnode,newStartVnode,insertedVnodeQueue,newCh,newStartIdx)canMove&&nodeOps.insertBefore(parentElm,oldEndVnode.elm,oldStartVnode.elm)oldEndVnode=oldCh[--oldEndIdx]newStartVnode=newCh[++newStartIdx]}else{如果(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,newCh,newStartIdx)oldCh[idxInOld]=undefinedcanMove&&nodeOps.insertBefore(parentElm,vnodeToMove.elm,oldStartVnode.elm)}else{//相同的键但不同的元素。视为新元素createElm(newStartVnode,insertedVnodeQueue,parentElm,oldStartVnode.elm,false,newCh,newStartIdx)}}newStartVnode=newCh[++newStartIdx]}}if(oldStartIdx>oldEndIdx){refElm=isUndef(newCh[newEndIdx+1)])?null:newCh[newEndIdx+1].elmaddVnodes(parentElm,refElm,newCh,newStartIdx,newEndIdx,insertedVnodeQueue)}elseif(newStartIdx>newEndIdx){removeVnodes(oldCh,oldStartIdx,oldEndIdx)}}的设计:isUndef对小时间判空functionisUndef(v){returnv===undefined||v===null}sameVnode判断节点是否相等判断新旧节点的key判断新旧节点的属性(tag,isComment表示是否为注释节点,isDef表示是否为非空节点,sameInputType表示是否相同Inputnode)是否一致判断新旧节点的加载asyncFactory函数是否一致?functionsameVnode(a,b){return(a.key===b.key&&((a.tag===b.tag&&a.isComment===b.isComment&&isDef(a.data)===isDef(b.data)&&sameInputType(a,b))||(isTrue(a.isAsyncPlaceholder)&&a.asyncFactory===b.asyncFactory&&isUndef(b.asyncFactory.error))))}patchVnode更新节点patchVnode更新节点主要做了以下几件事,代码太长就不贴了,影响读者,需要直接阅读源码:判断vnode和oldvnode是否相等,直接返回处理case静态节点的,如果vnode是patchable调用update判断vnode是否为根节点(即文本节点),如果是则转5,否则遍历更新其子节点判断是否为文本vnode和oldvnode的相同:如果不相同,则替换节点文本