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

Vue的diff流程图

流程前说明

  1. 由于diff的过程是对vnode(虚拟dom)树进行层级比较,所以以同一层级作为例子
  2. 下面将旧节点列表的起始和终止节点称为OS(OldStarVnode)和OE(OldEndVnode),用index标志遍历过程OS和OE的变化。即OS和OE的index称为OSIndex和OEIndex。同理得新节点的为NS和NE,NSIndex和NEIndex,如下图

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

主流程

如下图:

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

文字版描述一下就是:

  1. 判断是否遍历完,未遍历则开始2,否则,如果遍历完了旧节点列表,则未遍历的新节点则创建并且增加到节点列表,如果遍历完了新节点列表,则未遍历的旧节点在节点列表里面删除
  2. 对旧节点的OS和OE进行判空,如果为空,则跳过该节点,继续从1开始;否则继续3
  3. 对OS,OE,NS,NE进行两两比较,如果相等,则更新节点并且指针向下一个移动,继续从1开始;否则继续4
  4. 判断NS是否有key,有key则判断NS是否在旧节点列表里面找到key一样的进行更新;否则创建NS并且插入节点列表

updateChildren进行diff算法源码

  function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {

let oldStartIdx = 0

let newStartIdx = 0

let oldEndIdx = oldCh.length - 1

let oldStartVnode = oldCh[0]

let oldEndVnode = oldCh[oldEndIdx]

let newEndIdx = newCh.length - 1

let newStartVnode = newCh[0]

let newEndVnode = newCh[newEndIdx]

let oldKeyToIdx, idxInOld, vnodeToMove, refElm

// removeOnly is a special flag used only by <transition-group>

// to ensure removed elements stay in correct relative positions

// during leaving transitions

const canMove = !removeOnly

if (process.env.NODE_ENV !== 'production') {

checkDuplicateKeys(newCh)

}

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)

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(oldCh, oldStartIdx, oldEndIdx)

}

}

附,源码中部分工具函数的解释:

isUndef 对节点进行判空

function isUndef (v) {

return v === undefined || v === null

}

sameVnode对节点进行判断是否相等

  1. 判断新旧节点的key
  2. 判断新旧节点的属性(tag,isComment表示是否是注释节点,isDef表示是否为非空节点,sameInputType表示是否同个Input节点)是否一致
  3. 判断新旧节点的加载函数asyncFactory是否一致

function sameVnode (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更新节点主要做以下事情,代码比较长就不贴了,影响读者,需要可以直接阅读源码:

  1. 判断vnode和oldvnode是否相等,相等直接返回
  2. 处理静态节点的情况
  3. 对vnode如果是可patch的情形进行调用update
  4. 对vnode进行判断是否是根节点(即文本节点),如果是,则进行5,否则则对其子节点进行遍历更新
  5. 判断vnode和oldvnode文本是否一样: 不一样则替换节点文本

以上是 【JS】极为细致的Vue的Diff流程详解——以流程图表达 的全部内容, 来源链接: utcz.com/a/95631.html

回到顶部