亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Vue3 diff算法之雙端diff算法詳解

 更新時間:2022年09月04日 16:58:06   作者:無敵小書包  
雙端Diff在可以解決更多簡單Diff算法處理不了的場景,且比簡單Diff算法性能更好。本文將通過示例為大家詳細講講雙端diff算法的實現(xiàn)與使用,需要的可以參考一下

雙端Diff算法

雙端Diff在可以解決更多簡單Diff算法處理不了的場景,且比簡單Diff算法性能更好。本篇是以簡單Diff算法的案例來展開的,不過沒有了解簡單Diff算法直接看雙端Diff算法也是可以看明白的。

雙端比較的原理

引用簡單Diff算法的例子 - 案例一

oldVNode: {
    type: 'div',
    children: [
      { type: 'p', children: '1', key: '1' },
      { type: 'p', children: '2', key: '2' },
      { type: 'p', children: '3', key: '3' },
    ]
  },
  newVNode: {
    type: 'div',
    children: [
      { type: 'p', children: '3', key: '3' },
      { type: 'p', children: '1', key: '1' },
      { type: 'p', children: '2', key: '2' }
    ]
  }

簡單Diff的不足

這個案例使用簡單Diff算法來更新它,會發(fā)生兩次DOM移動操作。然而這并不是最優(yōu)解,其實只需要將 p - 3 節(jié)點移動到 p - 1 對應的真實節(jié)點前面就可以。

這是理論上的,簡單Diff算法并不能做到這一點,要想實現(xiàn)還得看雙端Diff算法。

雙端Diff介紹

顧名思義,雙端Diff算法就是同時對新舊兩組子節(jié)點兩個端點進行比較的算法,因此需要四個索引值指向新舊虛擬節(jié)點列表的兩端。

結合該案例來看雙端Diff的方式 - 案例二

  newChildren: [
    { type: 'p', children: '4', key: '4' },
    { type: 'p', children: '2', key: '2' },
    { type: 'p', children: '1', key: '1' },
    { type: 'p', children: '3', key: '3' }
  ],
  oldChildren: [
    { type: 'p', children: '1', key: '1' },
    { type: 'p', children: '2', key: '2' },
    { type: 'p', children: '3', key: '3' },
    { type: 'p', children: '4', key: '4' }
  ]

newStartIdx / newEndIdx / oldStartIdx / oldEndIdx四個指針分別指向 新節(jié)點的第一個元素 / 新節(jié)點的最后一個元素 / 舊節(jié)點的第一個元素 / 舊節(jié)點的最后一個元素,指向的這四個元素稱為 oldStartVNode / oldEndVNode / oldStartVNode / oldEndVNode。有了這些信息就可以互相比較了。

Diff流程

第一次diff

  • 第一步:比較舊的一組子節(jié)點中的第一個子節(jié)點 p-1 與新的一組子節(jié)點中的第一個子節(jié)點 P-4,看看它們是否相同。由于兩者的 key 值不同,因此不相同,不可復用,手是什么都不做。
  • 第二步:比較舊的一組子節(jié)點中的最后一個子節(jié)點 p-4 與新的一組子節(jié)點中的最后一個子節(jié)點 p-3,看看它們是否相同。由于兩者的 key 值不同,因此不相同,不可復用,于是什么都不做。
  • 第三步:比較日的一組子節(jié)點中的第一個子節(jié)點 p-1與新的一組子節(jié)點中的最后一個子節(jié)點 p-3,看看它們是否相同。由于兩者的 key 值不同,因此不相同,不可復用,于是什么都不做。
  • 第四步:比較舊的一組子節(jié)點中的最后一個子節(jié)點 p-4 與新的一組子節(jié)點中的第一個子節(jié)點p-4。由于它們的key 值相同,因此可以進行 DOM 復用。

在第四步找到了可以復用的節(jié)點,說明它的真實DOM節(jié)點可以復用。對于可復用的節(jié)點通過移動操作即可完成更新。

那么該如何移動呢?分析下第四步比較過程的細節(jié),第四步比較是比較舊的一組節(jié)點中的最后一個子節(jié)點 和 新的一組子節(jié)點中的第一個子節(jié)點。這說明節(jié)點 p - 4 原本是最后一個子節(jié)點,但在新的順序中,它變成了第一個子節(jié)點。對應到代碼,就是將索引 oldEndIdx 指向的虛擬節(jié)點所對應的真實DOM移動到索引 oldStartIdx 指向的虛擬節(jié)點所對應的真實DOM前面。

第一次比較完之后的情況如下:

code

第一次比較對應的代碼

function insert (el, container, anchor) {
  container.insertBefore(el, anchor)
}

function patchChildren (n1, n2, container) {
  if (typeof n2.children === 'string') {
  } else if (Array.isArray(n2.children)) {
    // 到這里說明子節(jié)點都是數(shù)組類型
    patchKeyedChildren(n1, n2, container)
  }
}

function patchKeyedChildren (n1, n2, container) {
  const oldChildren = n1.children
  const newChildren = n2.children
  // 四個端點指針
  let newStartIdx = 0
  let newEndIdx = newChildren.length - 1
  let oldStartIdx = 0
  let oldEndIdx = oldChildren.length - 1
  // 四個端點元素
  let newStartVNode = newChildren[newStartIdx]
  let newEndVNode = newChildren[newEndIdx]
  let oldStartVNode = oldChildren[oldStartIdx]
  let oldEndVNode = oldChildren[oldEndIdx]

  // 開始Diff
  if (oldStartVNode.key === newStartVNode.key) {
  //  第一步 oldStartVNode - newStartVNode
  } else if (oldEndVNode.key === newEndVNode.key) {
    // 第二步 oldEndVNode - newEndVNode
  } else if (oldStartVNode.key === newEndVNode.key) {
    // 第三步 oldStartVNode - newEndVNode
  } else if (oldEndVNode.key === newStartVNode.key) {
    // 第四步 oldEndVNode - newStartVNode

    // 調(diào)用patch打補丁
    patch(oldEndVNode, newStartVNode, container)
    // 移動DOM操作    oldEndVNode.el移動 到 oldStartVNode.el 前面
    insert(oldEndVNode.el, container, oldStartVNode.el)
    // 移動DOM操作完成后
    oldEndVNode = oldChildren[--oldEndVNode]
    newStartVNode = newChildren[++newStartVNode]
  }
}

本次Diff完成之后還有其他節(jié)點需要繼續(xù)進行,所以需要將diff的過程放入while循環(huán)中。滿足以下情況時,說明所有節(jié)點更新完畢,因此while的條件為 oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx

第二次diff

第二次diff的情況如圖

  • 第一步:比較舊的一組子節(jié)點中的頭部節(jié)點 p-1 與新的一組子節(jié)點中的頭部節(jié)點 p-2,看看它們是否相同。由于兩者的key 值不同,不可復用,所以什么都不做。(頭部節(jié)點:頭部素引 oldstartIdx 和 newstartIdx 所指向的節(jié)點)
  • 第二步:比較1的一組子節(jié)點中的尾部節(jié)點 p-3與新的一組子節(jié)點中的尾部節(jié)點 p-3,兩者的 key 值相同,可以復用。另外,由于防者都處于尾部,因此不需要對真實 DOM進行移動操作,只需要打補丁即可。

第二次diff后新舊節(jié)點的狀態(tài)如下

code

第二次比較對應代碼

while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    // 開始Diff
    if (oldStartVNode.key === newStartVNode.key) {
      //  第一步 oldStartVNode - newStartVNode
    } else if (oldEndVNode.key === newEndVNode.key) {
      // 第二步 oldEndVNode - newEndVNode

      // 調(diào)用patch打補丁
      patch(oldEndVNode, newEndVNode, container)
      oldEndVNode = oldChildren[--oldEndIdx]
      newEndVNode = newChildren[--newEndIdx]
    } else if (oldStartVNode.key === newEndVNode.key) {
      // 第三步 oldStartVNode - newEndVNode
    } else if (oldEndVNode.key === newStartVNode.key) {
      // 第四步 oldEndVNode - newStartVNode

      // 調(diào)用patch打補丁
      patch(oldEndVNode, newStartVNode, container)
      // 移動DOM操作    oldEndVNode.el移動 到 oldStartVNode.el 前面
      insert(oldEndVNode.el, container, oldStartVNode.el)
      // 移動DOM操作完成后
      oldEndVNode = oldChildren[--oldEndVNode]
      newStartVNode = newChildren[++newStartVNode]
    }
  }

第三次diff

第三次diff的情況如圖

  • 第一步:比較舊的一組子節(jié)點中的頭部節(jié)點p-1 與新的組子節(jié)點的頭部節(jié)點 p-2,看看它們是否相同。由于兩者key值不同 不可復用,因此什么都不做。
  • 第二步:比較舊的一組子節(jié)點中的尾部節(jié)點 p-2與新的一組子節(jié)點中的尾部節(jié)點 p-1看看它們是否相同,,由于兩者key值不 同不可復用,因此什么都不做。
  • 第三步:比較舊的一組子節(jié)點中的頭部節(jié)點p-1與新的一組子節(jié)申的尾部節(jié)點p-1,兩者的key 值相同,可以復用。

第三步比較中找到了相同節(jié)點,說明 p - 1 原本時頭部節(jié)點,但在新的順序中,它變成了尾部節(jié)點。因此將p - 1對應的真實DOM移動到舊的子節(jié)點的尾部節(jié)點 p - 2 對應的真實DOM后面

第三次diff后的新舊節(jié)點狀態(tài)如下圖

code

第三次比較對應代碼

while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    // 開始Diff
    if (oldStartVNode.key === newStartVNode.key) {
      //  第一步 oldStartVNode - newStartVNode
    } else if (oldEndVNode.key === newEndVNode.key) {
      // 第二步 oldEndVNode - newEndVNode

      // 調(diào)用patch打補丁
      patch(oldEndVNode, newEndVNode, container)
      oldEndVNode = oldChildren[--oldEndIdx]
      newEndVNode = newChildren[--newEndIdx]
    } else if (oldStartVNode.key === newEndVNode.key) {
      // 第三步 oldStartVNode - newEndVNode

      patch(oldStartVNode, newEndVNode, container)
      insert(oldStartVNode.el, container, newEndVNode.el.nextSibling)
      oldStartVNode = oldChildren[++oldStartIdx]
      newEndVNode = newChildren[--newEndIdx]
    } else if (oldEndVNode.key === newStartVNode.key) {
      // 第四步 oldEndVNode - newStartVNode

      // 調(diào)用patch打補丁
      patch(oldEndVNode, newStartVNode, container)
      // 移動DOM操作    oldEndVNode.el移動 到 oldStartVNode.el 前面
      insert(oldEndVNode.el, container, oldStartVNode.el)
      // 移動DOM操作完成后
      oldEndVNode = oldChildren[--oldEndVNode]
      newStartVNode = newChildren[++newStartVNode]
    }
  }

第四次diff

第三次diff的情況如圖

第一步:比較舊的一組子節(jié)點的頭部節(jié)點 p - 2 與新的一組子節(jié)點中的頭部節(jié)點 p - 2。發(fā)現(xiàn)兩者key值相同,可以復用。但兩者在新舊兩組子節(jié)點中都是頭部節(jié)點,因此不需要移動,只需要patch函數(shù)打補丁即可。

diff結果如圖

最后while條件為假,退出diff

code

while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    // 開始Diff
    if (oldStartVNode.key === newStartVNode.key) {
      //  第一步 oldStartVNode - newStartVNode

      patch(oldStartVNode, newStartVNode, container)
      oldStartVNode = oldChildren[++oldStartIdx]
      newStartVNode = newChildren[++newStartIdx]
    } else if (oldEndVNode.key === newEndVNode.key) {
      // 第二步 oldEndVNode - newEndVNode

      // 調(diào)用patch打補丁
      patch(oldEndVNode, newEndVNode, container)
      oldEndVNode = oldChildren[--oldEndIdx]
      newEndVNode = newChildren[--newEndIdx]
    } else if (oldStartVNode.key === newEndVNode.key) {
      // 第三步 oldStartVNode - newEndVNode

      patch(oldStartVNode, newEndVNode, container)
      insert(oldStartVNode.el, container, newEndVNode.el.nextSibling)
      oldStartVNode = oldChildren[++oldStartIdx]
      newEndVNode = newChildren[--newEndIdx]
    } else if (oldEndVNode.key === newStartVNode.key) {
      // 第四步 oldEndVNode - newStartVNode

      // 調(diào)用patch打補丁
      patch(oldEndVNode, newStartVNode, container)
      // 移動DOM操作    oldEndVNode.el移動 到 oldStartVNode.el 前面
      insert(oldEndVNode.el, container, oldStartVNode.el)
      // 移動DOM操作完成后
      oldEndVNode = oldChildren[--oldEndVNode]
      newStartVNode = newChildren[++newStartVNode]
    }
  }

雙端Diff的優(yōu)勢

回到最開始的例子,使用雙端Diff比較的時候,第一次循環(huán)的步驟四就能找到對應 p - 3的節(jié)點,然后將其移動到p - 1之前。

只需要一次DOM操作就能完成更新。

非理想情況的處理方式

上面是一個比較理想的例子,四個步驟分別都能匹配到對應的元素,實際上并非所有情況都能匹配到。

比如 - 案例三

  newChildren: [
    { type: 'p', children: '2', key: '2' },
    { type: 'p', children: '4', key: '4' },
    { type: 'p', children: '1', key: '1' },
    { type: 'p', children: '3', key: '3' }
  ],
  oldChildren: [
    { type: 'p', children: '1', key: '1' },
    { type: 'p', children: '2', key: '2' },
    { type: 'p', children: '3', key: '3' },
    { type: 'p', children: '4', key: '4' }
  ]

進行第一輪比較時,無法命中四個步驟中的任何一步。

  • p - 1 === p - 2
  • p - 4 === p - 3
  • p - 1 === p - 3
  • p - 4 === p - 2

因此只能通過額外的步驟來處理非理想情況。頭部尾部都不能命中,那么舊看非頭/尾部的節(jié)點能否命中,拿新的一組子節(jié)點的頭部節(jié)點去舊的一組子節(jié)點中查找。

在舊的一組子節(jié)點中,找到與新的一組子節(jié)點頭部節(jié)點具有相同key值的節(jié)點,意味著要將找到的子節(jié)點移動到真實DOM的頭部。

查找結果如圖

code

對應功能代碼

while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    // 開始Diff
    if (oldStartVNode.key === newStartVNode.key) {
      //  第一步 oldStartVNode - newStartVNode
    } else if (oldEndVNode.key === newEndVNode.key) {
      // 第二步 oldEndVNode - newEndVNode
    } else if (oldStartVNode.key === newEndVNode.key) {
      // 第三步 oldStartVNode - newEndVNode
    } else if (oldEndVNode.key === newStartVNode.key) {
      // 第四步 oldEndVNode - newStartVNode
    } else {
      // 上面四個步驟都沒有命中

      // 遍歷舊的一組子節(jié)點,視圖尋找與newStartVNode擁有相同key值的節(jié)點
      // idInOld就是新的一組子節(jié)點的頭部節(jié)點在舊的一組子節(jié)點中的索引
      const idInOld = oldChildren.findIndex(
        node => node.key === newStartVNode.key
      )
      // idInOld > 0說明找到了可復用的節(jié)點,并且需要將其對應的真實DOM移動到頭部
      if (idInOld > 0) {
        // 找到匹配到的節(jié)點,也就是需要移動的節(jié)點
        const vNodeToMove = oldChildren[idInOld]
        // 調(diào)用 patch 更新
        patch(vNodeToMove, newStartVNode, container)
        // 將vNodeToMove插入到頭部節(jié)點oldStartVNode.el之前
        insert(vNodeToMove.el, container, oldStartVNode.el)
        // 由于位置 idInOld 處的節(jié)點對應的真實DOM已經(jīng)發(fā)生移動,因此將其設置為 undefined
        oldChildren[idInOld] = undefined
        // 最后更新 newStartVNode 到下一個位置
        newStartVNode = newChildren[++newStartVNode]
      }

    }
  }

接下來第二個和第三個新節(jié)點都可以通過雙端對比找到匹配的節(jié)點,過程與 第二個案例 同理。

直到最后一個節(jié)點,此時舊節(jié)點列表的頭部節(jié)點是 undefined,因為該節(jié)點已經(jīng)被處理過了,所以不需要再處理它,直接跳過即可。

while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    // 開始Diff
    // 如果頭部或尾部節(jié)點為undefined,說明已經(jīng)處理過了,直接跳過
    if (!oldStartVNode) {
      oldStartVNode = oldChildren[++oldStartIdx]
    } else if (!oldEndVNode) {
      oldEndVNode = oldChildren[--oldEndIdx]
    } else if (oldStartVNode.key === newStartVNode.key) {
      //  第一步 oldStartVNode - newStartVNode
    } else if (oldEndVNode.key === newEndVNode.key) {
      // 第二步 oldEndVNode - newEndVNode
    } else if (oldStartVNode.key === newEndVNode.key) {
      // 第三步 oldStartVNode - newEndVNode
    } else if (oldEndVNode.key === newStartVNode.key) {
      // 第四步 oldEndVNode - newStartVNode
    } else {
      }
    }
  }

接下來的情況與上個案例同理。

添加新元素

當我們拿新的一組子節(jié)點中的頭部節(jié)點去舊的一組子節(jié)點中尋找可復用節(jié)點時,并不是一定能找到。

案例四

newChildren: [
    { type: 'p', children: '4', key: '4' },
    { type: 'p', children: '1', key: '1' },
    { type: 'p', children: '3', key: '3' },
    { type: 'p', children: '2', key: '2' }
  ],
  oldChildren: [
    { type: 'p', children: '1', key: '1' },
    { type: 'p', children: '2', key: '2' },
    { type: 'p', children: '3', key: '3' }
  ]

雙端查找沒有匹配到

循環(huán)查找也沒有匹配到

p - 4經(jīng)過雙端對比,循環(huán)查找都沒有找到匹配的節(jié)點,說明 p - 4 是一個新增節(jié)點,應該將它創(chuàng)建并掛載到正確的位置。

那么應該掛載到什么位置呢,因為節(jié)點p - 4時新的一組子節(jié)點中的頭部節(jié)點,所以只需要將它掛載到當前頭部節(jié)點之前即可。頭部節(jié)點指的是舊的一組子節(jié)點中的頭部節(jié)點對應的真實DOM節(jié)點 p - 1。

while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    // 開始Diff
    if (oldStartVNode.key === newStartVNode.key) {
      //  第一步 oldStartVNode - newStartVNode
    } else if (oldEndVNode.key === newEndVNode.key) {
      // 第二步 oldEndVNode - newEndVNode
    } else if (oldStartVNode.key === newEndVNode.key) {
      // 第三步 oldStartVNode - newEndVNode
    } else if (oldEndVNode.key === newStartVNode.key) {
      // 第四步 oldEndVNode - newStartVNode
    } else {
      // 上面四個步驟都沒有命中

      // 遍歷舊的一組子節(jié)點,視圖尋找與newStartVNode擁有相同key值的節(jié)點
      // idInOld就是新的一組子節(jié)點的頭部節(jié)點在舊的一組子節(jié)點中的索引
      const idInOld = oldChildren.findIndex(
        node => node.key === newStartVNode.key
      )
      // idInOld > 0說明找到了可復用的節(jié)點,并且需要將其對應的真實DOM移動到頭部
      if (idInOld > 0) {
        // 找到匹配到的節(jié)點,也就是需要移動的節(jié)點
        const vNodeToMove = oldChildren[idInOld]
        // 調(diào)用 patch 更新
        patch(vNodeToMove, newStartVNode, container)
        // 將vNodeToMove插入到頭部節(jié)點oldStartVNode.el之前
        insert(vNodeToMove.el, container, oldStartVNode.el)
        // 由于位置 idInOld 處的節(jié)點對應的真實DOM已經(jīng)發(fā)生移動,因此將其設置為 undefined
        oldChildren[idInOld] = undefined
        // 最后更新 newStartVNode 到下一個位置
        newStartVNode = newChildren[++newStartVNode]
      } else {
        // 將 newStartVNode作為新節(jié)點掛載到頭部,使用當前頭部節(jié)點 oldStartVNode.el 作為錨點
        patch(null, newStartVNode, container, oldStartVNode.el)
      }
      // 更新頭部節(jié)點
      newStartVNode = newChildren[++newStartIdx]
    }
  }

當條件 idInOld > 0不成立時,說明沒有可以復用的節(jié)點,又由于newStartVNode是頭部節(jié)點,因此應該將其作為新的頭部節(jié)點進行掛載操作。

剩下節(jié)點的操作類似于案例二。

新增節(jié)點可能在最后一次匹配到 - 案例五

  newChildren: [
    { type: 'p', children: '4', key: '4' },
    { type: 'p', children: '1', key: '1' },
    { type: 'p', children: '2', key: '2' },
    { type: 'p', children: '3', key: '3' }
  ],
  oldChildren: [
    { type: 'p', children: '1', key: '1' },
    { type: 'p', children: '2', key: '2' },
    { type: 'p', children: '3', key: '3' }
  ]

這里省去雙端匹配的流程

由于變量oldStartIdx 的值大于oldEndIdx的值,滿足更新停止的條件,更新停止了。但看圖可知,p - 4在整個更新過程中被一樓了,沒有得到任何處理。

因此可得:新的一組子節(jié)點中有一六得節(jié)點需要作為新節(jié)點掛載。索引值位于newStartIdx 和 newEndIdx 之間得所有節(jié)點都是新節(jié)點。掛載時得錨點仍然使用當前頭部節(jié)點 oldStartVNode.el。

因為此時舊節(jié)點列表中得節(jié)點可能為undefined,因此可能出現(xiàn)問題;但新節(jié)點列表得順序已經(jīng)被更新了,所以更新過得新節(jié)點對應得真實DOM順序都已經(jīng)重新牌列,便可以取錨點:

const anchor = newChildren[newEndIdx + 1] ? newChildren[newEndIdx + 1].el : null

  while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
  
  }
  // 說明有新節(jié)點需要掛載
  if (oldEndIdx < oldStartIdx && newStartIdx <= newEndIdx) {
    for (let i = newStartIdx; i < newEndIdx; i++) {
      const anchor = newChildren[newEndIdx + 1] ? newChildren[newEndIdx + 1].el : null
      patch(null, newChildren[i], container, anchor)
    }
  }

移除不存在得節(jié)點

除了新增節(jié)點得問題后,還可能存在需要移除的節(jié)點 - 案例六

  newChildren: [
    { type: 'p', children: '1', key: '1' },
    { type: 'p', children: '3', key: '3' }
  ],
  oldChildren: [
    { type: 'p', children: '1', key: '1' },
    { type: 'p', children: '2', key: '2' },
    { type: 'p', children: '3', key: '3' }
  ]

第一次、第二次循環(huán)的步驟于案例二同理

新節(jié)點更新完之后,發(fā)現(xiàn)還存在未處理的舊節(jié)點,這便是需要刪除的節(jié)點。

while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
  
  }
  // 說明有新節(jié)點需要掛載
  if (oldEndIdx < oldStartIdx && newStartIdx <= newEndIdx) {
    for (let i = newStartIdx; i < newEndIdx; i++) {
      const anchor = newChildren[newEndIdx + 1] ? newChildren[newEndIdx + 1].el : null
      patch(null, newChildren[i], container, anchor)
    }
  } else if (newEndIdx < newStartIdx && oldStartIdx <= oldEndIdx) {
    // 有需要刪除的節(jié)點
    for (let i = oldStartIdx; i < oldEndIdx; i++) {
      unmount(oldChildren[i])
    }
  }

雙端Diff完整代碼

function patchKeyedChildren (n1, n2, container) {
  const oldChildren = n1.children
  const newChildren = n2.children
  // 四個端點指針
  let newStartIdx = 0
  let newEndIdx = newChildren.length - 1
  let oldStartIdx = 0
  let oldEndIdx = oldChildren.length - 1
  // 四個端點元素
  let newStartVNode = newChildren[newStartIdx]
  let newEndVNode = newChildren[newEndIdx]
  let oldStartVNode = oldChildren[oldStartIdx]
  let oldEndVNode = oldChildren[oldEndIdx]

  while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    // 開始Diff
    // 如果頭部或尾部節(jié)點為undefined,說明已經(jīng)處理過了,直接跳過
    if (!oldStartVNode) {
      oldStartVNode = oldChildren[++oldStartIdx]
    } else if (!oldEndVNode) {
      oldEndVNode = oldChildren[--oldEndIdx]
    } else if (oldStartVNode.key === newStartVNode.key) {
      //  第一步 oldStartVNode - newStartVNode

      patch(oldStartVNode, newStartVNode, container)
      oldStartVNode = oldChildren[++oldStartIdx]
      newStartVNode = newChildren[++newStartIdx]
    } else if (oldEndVNode.key === newEndVNode.key) {
      // 第二步 oldEndVNode - newEndVNode

      // 調(diào)用patch打補丁
      patch(oldEndVNode, newEndVNode, container)
      oldEndVNode = oldChildren[--oldEndIdx]
      newEndVNode = newChildren[--newEndIdx]
    } else if (oldStartVNode.key === newEndVNode.key) {
      // 第三步 oldStartVNode - newEndVNode

      patch(oldStartVNode, newEndVNode, container)
      insert(oldStartVNode.el, container, newEndVNode.el.nextSibling)
      oldStartVNode = oldChildren[++oldStartIdx]
      newEndVNode = newChildren[--newEndIdx]
    } else if (oldEndVNode.key === newStartVNode.key) {
      // 第四步 oldEndVNode - newStartVNode

      // 調(diào)用patch打補丁
      patch(oldEndVNode, newStartVNode, container)
      // 移動DOM操作    oldEndVNode.el移動 到 oldStartVNode.el 前面
      insert(oldEndVNode.el, container, oldStartVNode.el)
      // 移動DOM操作完成后
      oldEndVNode = oldChildren[--oldEndVNode]
      newStartVNode = newChildren[++newStartVNode]
    } else {
      // 上面四個步驟都沒有命中

      // 遍歷舊的一組子節(jié)點,視圖尋找與newStartVNode擁有相同key值的節(jié)點
      // idInOld就是新的一組子節(jié)點的頭部節(jié)點在舊的一組子節(jié)點中的索引
      const idInOld = oldChildren.findIndex(
        node => node.key === newStartVNode.key
      )
      // idInOld > 0說明找到了可復用的節(jié)點,并且需要將其對應的真實DOM移動到頭部
      if (idInOld > 0) {
        // 找到匹配到的節(jié)點,也就是需要移動的節(jié)點
        const vNodeToMove = oldChildren[idInOld]
        // 調(diào)用 patch 更新
        patch(vNodeToMove, newStartVNode, container)
        // 將vNodeToMove插入到頭部節(jié)點oldStartVNode.el之前
        insert(vNodeToMove.el, container, oldStartVNode.el)
        // 由于位置 idInOld 處的節(jié)點對應的真實DOM已經(jīng)發(fā)生移動,因此將其設置為 undefined
        oldChildren[idInOld] = undefined
        // 最后更新 newStartVNode 到下一個位置
        newStartVNode = newChildren[++newStartVNode]
      } else {
        // 將 newStartVNode作為新節(jié)點掛載到頭部,使用當前頭部節(jié)點 oldStartVNode.el 作為錨點
        patch(null, newStartVNode, container, oldStartVNode.el)
      }
      // 更新頭部節(jié)點
      newStartVNode = newChildren[++newStartIdx]
    }
  }

  if (oldEndIdx < oldStartIdx && newStartIdx <= newEndIdx) {
    // 說明有新節(jié)點需要掛載
    for (let i = newStartIdx; i < newEndIdx; i++) {
      const anchor = newChildren[newEndIdx + 1] ? newChildren[newEndIdx + 1].el : null
      patch(null, newChildren[i], container, anchor)
    }
  } else if (newEndIdx < newStartIdx && oldStartIdx <= oldEndIdx) {
    // 有需要刪除的節(jié)點
    for (let i = oldStartIdx; i < oldEndIdx; i++) {
      unmount(oldChildren[i])
    }
  }
}

以上就是Vue3 diff算法之雙端diff算法詳解的詳細內(nèi)容,更多關于Vue3雙端diff算法的資料請關注腳本之家其它相關文章!

相關文章

最新評論