極爲細緻的Vue的Diff流程詳解——以流程圖表達

網上看了一些diff的算法,可是感受看完以後,仍是那麼的只知其一;不知其二,爲何一個簡單的diff算法,不能直接畫個流程圖就簡單的明瞭了呢,說動就動,下面的是本人基於vue版本2.6.11源碼爲各位讀友進行的解析

Vue的diff流程圖

流程前說明

  1. 因爲diff的過程是對vnode(虛擬dom)樹進行層級比較,因此以同一層級做爲例子
  2. 下面將舊節點列表的起始和終止節點稱爲OS(OldStarVnode)和OE(OldEndVnode),用index標誌遍歷過程OS和OE的變化。即OS和OE的index稱爲OSIndex和OEIndex。同理得新節點的爲NS和NE,NSIndex和NEIndex,以下圖

image

主流程

以下圖:vue

image

文字版描述一下就是:node

  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文本是否同樣: 不同則替換節點文本
相關文章
相關標籤/搜索