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
functionupdateChildren(parentElm,oldCh,newCh,insertedVnodeQueue,removeOnly){letoldStartIdx=0// 旧头索引letnewStartIdx=0// 新头索引letoldEndIdx=oldCh.length-1// 旧尾索引letnewEndIdx=newCh.length-1// 新尾索引letoldStartVnode=oldCh[0]// oldVnode的第一个childletoldEndVnode=oldCh[oldEndIdx]// oldVnode的最后一个childletnewStartVnode=newCh[0]// newVnode的第一个childletnewEndVnode=newCh[newEndIdx]// newVnode的最后一个childletoldKeyToIdx,idxInOld,vnodeToMove,refElm// removeOnly is a special flag used only by <transition-group>// to ensure removed elements stay in correct relative positions// during leaving transitionsconstcanMove=!removeOnly// 如果oldStartVnode和oldEndVnode重合,并且新的也都重合了,证明diff完了,循环结束while(oldStartIdx<=oldEndIdx&&newStartIdx<=newEndIdx){// 如果oldVnode的第一个child不存在if(isUndef(oldStartVnode)){// oldStart索引右移oldStartVnode=oldCh[++oldStartIdx]// Vnode has been moved left// 如果oldVnode的最后一个child不存在}elseif(isUndef(oldEndVnode)){// oldEnd索引左移oldEndVnode=oldCh[--oldEndIdx]// oldStartVnode和newStartVnode是同一个节点}elseif(sameVnode(oldStartVnode,newStartVnode)){// patch oldStartVnode和newStartVnode, 索引左移,继续循环patchVnode(oldStartVnode,newStartVnode,insertedVnodeQueue)oldStartVnode=oldCh[++oldStartIdx]newStartVnode=newCh[++newStartIdx]// oldEndVnode和newEndVnode是同一个节点}elseif(sameVnode(oldEndVnode,newEndVnode)){// patch oldEndVnode和newEndVnode,索引右移,继续循环patchVnode(oldEndVnode,newEndVnode,insertedVnodeQueue)oldEndVnode=oldCh[--oldEndIdx]newEndVnode=newCh[--newEndIdx]// oldStartVnode和newEndVnode是同一个节点}elseif(sameVnode(oldStartVnode,newEndVnode)){// Vnode moved right// patch oldStartVnode和newEndVnodepatchVnode(oldStartVnode,newEndVnode,insertedVnodeQueue)// 如果removeOnly是false,则将oldStartVnode.eml移动到oldEndVnode.elm之后canMove&&nodeOps.insertBefore(parentElm,oldStartVnode.elm,nodeOps.nextSibling(oldEndVnode.elm))// oldStart索引右移,newEnd索引左移oldStartVnode=oldCh[++oldStartIdx]newEndVnode=newCh[--newEndIdx]// 如果oldEndVnode和newStartVnode是同一个节点}elseif(sameVnode(oldEndVnode,newStartVnode)){// Vnode moved left// patch oldEndVnode和newStartVnodepatchVnode(oldEndVnode,newStartVnode,insertedVnodeQueue)// 如果removeOnly是false,则将oldEndVnode.elm移动到oldStartVnode.elm之前canMove&&nodeOps.insertBefore(parentElm,oldEndVnode.elm,oldStartVnode.elm)// oldEnd索引左移,newStart索引右移oldEndVnode=oldCh[--oldEndIdx]newStartVnode=newCh[++newStartIdx]// 如果都不匹配}else{if(isUndef(oldKeyToIdx))oldKeyToIdx=createKeyToOldIdx(oldCh,oldStartIdx,oldEndIdx)// 尝试在oldChildren中寻找和newStartVnode的具有相同的key的VnodeidxInOld=isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode,oldCh,oldStartIdx,oldEndIdx)// 如果未找到,说明newStartVnode是一个新的节点if(isUndef(idxInOld)){// New element// 创建一个新VnodecreateElm(newStartVnode,insertedVnodeQueue,parentElm,oldStartVnode.elm)// 如果找到了和newStartVnodej具有相同的key的Vnode,叫vnodeToMove}else{vnodeToMove=oldCh[idxInOld]/* istanbul ignore if */if(process.env.NODE_ENV!=='production'&&!vnodeToMove){warn('It seems there are duplicate keys that is causing an update error. '+'Make sure each v-for item has a unique key.')}// 比较两个具有相同的key的新节点是否是同一个节点//不设key,newCh和oldCh只会进行头尾两端的相互比较,设key后,除了头尾两端的比较外,还会从用key生成的对象oldKeyToIdx中查找匹配的节点,所以为节点设置key可以更高效的利用dom。if(sameVnode(vnodeToMove,newStartVnode)){// patch vnodeToMove和newStartVnodepatchVnode(vnodeToMove,newStartVnode,insertedVnodeQueue)// 清除oldCh[idxInOld]=undefined// 如果removeOnly是false,则将找到的和newStartVnodej具有相同的key的Vnode,叫vnodeToMove.elm// 移动到oldStartVnode.elm之前canMove&&nodeOps.insertBefore(parentElm,vnodeToMove.elm,oldStartVnode.elm)// 如果key相同,但是节点不相同,则创建一个新的节点}else{// same key but different element. treat as new elementcreateElm(newStartVnode,insertedVnodeQueue,parentElm,oldStartVnode.elm)}}// 右移newStartVnode=newCh[++newStartIdx]}}
一、是什么
diff
算法是一种通过同层的树节点进行比较的高效算法其有两个特点:
diff
算法的在很多场景下都有应用,在vue
中,作用于虚拟dom
渲染成真实dom
的新旧VNode
节点比较二、比较方式
diff
整体策略为:深度优先,同层比较下面举个
vue
通过diff
算法更新的例子:新旧
VNode
节点如下图所示:第一次循环后,发现旧节点D与新节点D相同,直接复用旧节点D作为
diff
后的第一个真实节点,同时旧节点endIndex
移动到C,新节点的startIndex
移动到了 C第二次循环后,同样是旧节点的末尾和新节点的开头(都是 C)相同,同理,
diff
后创建了 C 的真实节点插入到第一次创建的 B 节点后面。同时旧节点的endIndex
移动到了 B,新节点的startIndex
移动到了 E第三次循环中,发现E没有找到,这时候只能直接创建新的真实节点 E,插入到第二次创建的 C 节点之后。同时新节点的
startIndex
移动到了 A。旧节点的startIndex
和endIndex
都保持不动第四次循环中,发现了新旧节点的开头(都是 A)相同,于是
diff
后创建了 A 的真实节点,插入到前一次创建的 E 节点后面。同时旧节点的startIndex
移动到了 B,新节点的startIndex
移动到了 B第五次循环中,情形同第四次循环一样,因此
diff
后创建了 B 真实节点 插入到前一次创建的 A 节点后面。同时旧节点的startIndex
移动到了 C,新节点的 startIndex 移动到了 F新节点的
startIndex
已经大于endIndex
了,需要创建newStartIdx
和newEndIdx
之间的所有节点,也就是节点F,直接创建 F 节点对应的真实节点放到 B 节点后面三、原理分析
当数据发生改变时,
set
方法会调用Dep.notify
通知所有订阅者Watcher
,订阅者就会调用patch
给真实的DOM
打补丁,更新相应的视图源码位置:src/core/vdom/patch.js
patch
函数前两个参数位为oldVnode
和Vnode
,分别代表新的节点和之前的旧节点,主要做了四个判断:destory
钩子createElm
sameVnode
判断节点是否一样,一样时,直接调用patchVnode
去处理这两个节点下面主要讲的是
patchVnode
部分patchVnode
主要做了几个判断:dom
的文本内容为新节点的文本内容DOM
,并且添加进父节点DOM
删除子节点不完全一致,则调用
updateChildren
while
循环主要处理了以下五种情景:VNode
节点的start
相同时,直接patchVnode
,同时新老VNode
节点的开始索引都加 1VNode
节点的end
相同时,同样直接patchVnode
,同时新老VNode
节点的结束索引都减 1VNode
节点的start
和新VNode
节点的end
相同时,这时候在patchVnode
后,还需要将当前真实dom
节点移动到oldEndVnode
的后面,同时老VNode
节点开始索引加 1,新VNode
节点的结束索引减 1VNode
节点的end
和新VNode
节点的start
相同时,这时候在patchVnode
后,还需要将当前真实dom
节点移动到oldStartVnode
的前面,同时老VNode
节点结束索引减 1,新VNode
节点的开始索引加 1VNode
为key
值,对应index
序列为value
值的哈希表中找到与newStartVnode
一致key
的旧的VNode
节点,再进行patchVnode
,同时将这个真实dom
移动到oldStartVnode
对应的真实dom
的前面createElm
创建一个新的dom
节点放到当前newStartIdx
的位置小结
watcher
就会调用patch
给真实的DOM
打补丁isSameVnode
进行判断,相同则调用patchVnode
方法patchVnode
做了以下操作:dom
,称为el
el
文本节点设置为Vnode
的文本节点oldVnode
有子节点而VNode
没有,则删除el
子节点oldVnode
没有子节点而VNode
有,则将VNode
的子节点真实化后添加到el
updateChildren
函数比较子节点updateChildren
主要做了以下操作:VNode
的头尾指针patchVnode
进行patch
重复流程、调用createElem
创建一个新节点,从哈希表寻找key
一致的VNode
节点再分情况操作参考文献
The text was updated successfully, but these errors were encountered: