怎么深入解析Vue3中的diff算法

今天給大家介紹一下怎么深入解析Vue3中的diff 算法。文章的內(nèi)容小編覺(jué)得不錯(cuò),現(xiàn)在給大家分享一下,覺(jué)得有需要的朋友可以了解一下,希望對(duì)大家有所幫助,下面跟著小編的思路一起來(lái)閱讀吧。

為陳巴爾虎等地區(qū)用戶(hù)提供了全套網(wǎng)頁(yè)設(shè)計(jì)制作服務(wù),及陳巴爾虎網(wǎng)站建設(shè)行業(yè)解決方案。主營(yíng)業(yè)務(wù)為網(wǎng)站制作、成都網(wǎng)站建設(shè)、陳巴爾虎網(wǎng)站設(shè)計(jì),以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專(zhuān)業(yè)、用心的態(tài)度為用戶(hù)提供真誠(chéng)的服務(wù)。我們深信只要達(dá)到每一位用戶(hù)的要求,就會(huì)得到認(rèn)可,從而選擇與我們長(zhǎng)期合作。這樣,我們也可以走得更遠(yuǎn)!

1.0  diff 無(wú)key子節(jié)點(diǎn)

在處理被標(biāo)記為UNKEYED_FRAGMENT時(shí)。

  • 首先會(huì)通過(guò)新舊自序列獲取最小共同長(zhǎng)度commonLength。

  • 對(duì)公共部分循環(huán)遍歷patch。

  • patch 結(jié)束,再處理剩余的新舊節(jié)點(diǎn)。

  • 如果oldLength > newLength,說(shuō)明需要對(duì)舊節(jié)點(diǎn)進(jìn)行unmount

  • 否則,說(shuō)明有新增節(jié)點(diǎn),需要進(jìn)行mount;

怎么深入解析Vue3中的diff 算法

這里貼下省略后的代碼。

const patchUnkeyedChildren = (c1, c2,...res) => {
    c1 = c1 || EMPTY_ARR
    c2 = c2 || EMPTY_ARR
    // 獲取新舊子節(jié)點(diǎn)的長(zhǎng)度
    const oldLength = c1.length
    const newLength = c2.length
    // 1. 取得公共長(zhǎng)度。最小長(zhǎng)度
    const commonLength = Math.min(oldLength, newLength)
    let i
    // 2. patch公共部分
    for (i = 0; i < commonLength; i++) { 
      patch(...)
    }
    // 3. 卸載舊節(jié)點(diǎn)
    if (oldLength > newLength) {
      // remove old
      unmountChildren(...)
    } else {
      // mount new
      // 4. 否則掛載新的子節(jié)點(diǎn)
      mountChildren(...)
    }
  }

從上面的代碼可以看出,在處理無(wú)key子節(jié)點(diǎn)的時(shí)候,邏輯還是非常簡(jiǎn)單粗暴的。準(zhǔn)確的說(shuō)處理無(wú)key子節(jié)點(diǎn)的效率并不高。

因?yàn)椴还苁侵苯訉?duì)公共部分patch,還是直接對(duì)新增節(jié)點(diǎn)進(jìn)行mountChildren(其實(shí)是遍歷子節(jié)點(diǎn),進(jìn)行patch操作),其實(shí)都是在遞歸進(jìn)行patch,這就會(huì)影響到性能。

2.0 diffkey子節(jié)點(diǎn)序列

diffkey子序列的時(shí)候,會(huì)進(jìn)行細(xì)分處理。主要會(huì)經(jīng)過(guò)以下一種情況的判斷:

  • 起始位置節(jié)點(diǎn)類(lèi)型相同。

  • 結(jié)束位置節(jié)點(diǎn)類(lèi)型相同。

  • 相同部分處理完,有新增節(jié)點(diǎn)。

  • 相同部分處理完,有舊節(jié)點(diǎn)需要卸載。

  • 首尾相同,但中間部分存在可復(fù)用亂序節(jié)點(diǎn)。

在開(kāi)始階段,會(huì)先生面三個(gè)指正,分別是:

  • i = 0,指向新舊序列的開(kāi)始位置

  • e1 = oldLength - 1,指向舊序列的結(jié)束位置

  • e2 = newLength - 1,指向新序列的結(jié)束位置

怎么深入解析Vue3中的diff 算法

let i = 0
const l2 = c2.length
let e1 = c1.length - 1 // prev ending index
let e2 = l2 - 1 // next ending index

下面開(kāi)始分情況進(jìn)行diff處理。

2.1 起始位置節(jié)點(diǎn)類(lèi)型相同

怎么深入解析Vue3中的diff 算法

  • 對(duì)于起始位置類(lèi)型相同的節(jié)點(diǎn),從左向右進(jìn)行diff遍歷。

  • 如果新舊節(jié)點(diǎn)類(lèi)型相同,則進(jìn)行patch處理

  • 節(jié)點(diǎn)類(lèi)型不同,則break,跳出遍歷diff

//  i <= 2 && i <= 3
while (i <= e1 && i <= e2) {
  const n1 = c1[i]
  const n2 = c2[i]
  if (isSameVNodeType(n1, n2)) {
    // 如果是相同的節(jié)點(diǎn)類(lèi)型,則進(jìn)行遞歸patch
    patch(...)
  } else {
    // 否則退出
    break
  }
  i++
}

上面上略了部分代碼,但不影響主要邏輯。

從代碼可以知道,遍歷時(shí),利用前面在函數(shù)全局上下文中聲明的三個(gè)指針,進(jìn)行遍歷判斷。

保證能充分遍歷到開(kāi)始位置相同的位置,i <= e1 && i <= e2

一旦遇到類(lèi)型不同的節(jié)點(diǎn),就會(huì)跳出diff遍歷。

2.2 結(jié)束位置節(jié)點(diǎn)類(lèi)型相同

怎么深入解析Vue3中的diff 算法

開(kāi)始位置相同diff 結(jié)束,會(huì)緊接著從序列尾部開(kāi)始遍歷 diff。

此時(shí)需要對(duì)尾指針e1、e2進(jìn)行遞減。

//  i <= 2 && i <= 3
// 結(jié)束后: e1 = 0 e2 =  1
while (i <= e1 && i <= e2) {
  const n1 = c1[e1]
  const n2 = c2[e2]
  if (isSameVNodeType(n1, n2)) {
    // 相同的節(jié)點(diǎn)類(lèi)型
    patch(...)
  } else {
    // 否則退出
    break
  }
  e1--
  e2--
}

從代碼可以看出,diff邏輯與第一種基本一樣,相同類(lèi)型進(jìn)行patch處理。

不同類(lèi)型break,跳出循環(huán)遍歷。

并且對(duì)尾指針進(jìn)行遞減操作。

2.3 相同部分遍歷結(jié)束,新序列中有新增節(jié)點(diǎn),進(jìn)行掛載

經(jīng)過(guò)上面兩種情況的處理,已經(jīng)patch完首尾相同部分的節(jié)點(diǎn),接下來(lái)是對(duì)新序列中的新增節(jié)點(diǎn)進(jìn)行patch處理。

怎么深入解析Vue3中的diff 算法

在經(jīng)過(guò)上面兩種請(qǐng)款處理之后,如果有新增節(jié)點(diǎn),可能會(huì)出現(xiàn) i >  e1 && i <= e2的情況。

這種情況下意味著新的子節(jié)點(diǎn)序列中有新增節(jié)點(diǎn)。

這時(shí)會(huì)對(duì)新增節(jié)點(diǎn)進(jìn)行patch

// 3. common sequence + mount
// (a b)
// (a b) c
// i = 2, e1 = 1, e2 = 2
// (a b)
// c (a b)
// i = 0, e1 = -1, e2 = 0
if (i > e1) {
  if (i <= e2) {
    const nextPos = e2 + 1
    // nextPos < l2,說(shuō)明有已經(jīng)patch過(guò)尾部節(jié)點(diǎn),
    // 否則會(huì)獲取父節(jié)點(diǎn)作為錨點(diǎn)
    const anchor = nextPos < l2 ? c2[nextPos].el : parentAnchor
    while (i <= e2) {
      patch(null, c2[i], anchor, ...others)
      i++
    }
  }
}

從上面的代碼可以知道,patch的時(shí)候沒(méi)有傳第一個(gè)參數(shù),最終會(huì)走mount的邏輯。

可以看這篇 主要分析patch的過(guò)程

https://mp.weixin.qq.com/s/hzpNGWFCLMC2vJNSmP2vsQ

patch的過(guò)程中,會(huì)遞增i指針。

并通過(guò)nextPos來(lái)獲取錨點(diǎn)。

如果nextPos < l2,則以已經(jīng)patch的節(jié)點(diǎn)作為錨點(diǎn),否則以父節(jié)點(diǎn)作為錨點(diǎn)。

2.4 相同部分遍歷結(jié)束,新序列中少節(jié)點(diǎn),進(jìn)行卸載

怎么深入解析Vue3中的diff 算法

如果處理完收尾相同的節(jié)點(diǎn),出現(xiàn)i > e2 && i <= e1的情況,則意味著有舊節(jié)點(diǎn)需要進(jìn)行卸載操作。

// 4. common sequence + unmount
// (a b) c
// (a b)
// i = 2, e1 = 2, e2 = 1
// a (b c)
// (b c)
// i = 0, e1 = 0, e2 = -1
// 公共序列 卸載舊的
else if (i > e2) {
  while (i <= e1) {
    unmount(c1[i], parentComponent, parentSuspense, true)
    i++
  }
}

通過(guò)代碼可以知道,這種情況下,會(huì)遞增i指針,對(duì)舊節(jié)點(diǎn)進(jìn)行卸載。

2.5 亂序情況

這種情況下較為復(fù)雜,但diff的核心邏輯在于通過(guò)新舊節(jié)點(diǎn)的位置變化構(gòu)建一個(gè)最大遞增子序列,最大子序列能保證通過(guò)最小的移動(dòng)或者patch實(shí)現(xiàn)節(jié)點(diǎn)的復(fù)用。

下面一起來(lái)看下如何實(shí)現(xiàn)的。

怎么深入解析Vue3中的diff 算法

2.5.1 為新子節(jié)點(diǎn)構(gòu)建key:index映射

怎么深入解析Vue3中的diff 算法

// 5. 亂序的情況
// [i ... e1 + 1]: a b [c d e] f g
// [i ... e2 + 1]: a b [e d c h] f g
// i = 2, e1 = 4, e2 = 5

const s1 = i // s1 = 2
const s2 = i // s2 = 2

// 5.1 build key:index map for newChildren
// 首先為新的子節(jié)點(diǎn)構(gòu)建在新的子序列中 key:index 的映射
// 通過(guò)map 創(chuàng)建的新的子節(jié)點(diǎn)
const keyToNewIndexMap = new Map()
// 遍歷新的節(jié)點(diǎn),為新節(jié)點(diǎn)設(shè)置key
// i = 2; i <= 5
for (i = s2; i <= e2; i++) {
  // 獲取的是新序列中的子節(jié)點(diǎn)
  const nextChild = c2[i];
  if (nextChild.key != null) {
    // nextChild.key 已存在
    // a b [e d c h] f g
    // e:2 d:3 c:4 h:5
    keyToNewIndexMap.set(nextChild.key, i)
  }
}

結(jié)合上面的圖和代碼可以知道:

  • 在經(jīng)過(guò)首尾相同的patch處理之后,i = 2,e1 = 4,e2 = 5

  • 經(jīng)過(guò)遍歷之后keyToNewIndexMap中,新節(jié)點(diǎn)的key:index的關(guān)系為 E : 2、D : 3 、C : 4、H : 5

  • keyToNewIndexMap的作用主要是后面通過(guò)遍歷舊子序列,確定可復(fù)用節(jié)點(diǎn)在新的子序列中的位置

2.5.2 從左向右遍歷舊子序列,patch匹配的相同類(lèi)型的節(jié)點(diǎn),移除不存在的節(jié)點(diǎn)

經(jīng)過(guò)前面的處理,已經(jīng)創(chuàng)建了keyToNewIndexMap。

在開(kāi)始從左向右遍歷之前,需要知道幾個(gè)變量的含義:

// 5.2 loop through old children left to be patched and try to patch
// matching nodes & remove nodes that are no longer present
// 從舊的子節(jié)點(diǎn)的左側(cè)開(kāi)始循環(huán)遍歷進(jìn)行patch。
// 并且patch匹配的節(jié)點(diǎn) 并移除不存在的節(jié)點(diǎn)

// 已經(jīng)patch的節(jié)點(diǎn)個(gè)數(shù)
let patched = 0
// 需要patch的節(jié)點(diǎn)數(shù)量
// 以上圖為例:e2 = 5; s2 = 2; 知道需要patch的節(jié)點(diǎn)個(gè)數(shù)
// toBePatched = 4
const toBePatched = e2 - s2 + 1
// 用于判斷節(jié)點(diǎn)是否需要移動(dòng)
// 當(dāng)新舊隊(duì)列中出現(xiàn)可復(fù)用節(jié)點(diǎn)交叉時(shí),moved = true
let moved = false
// used to track whether any node has moved
// 用于記錄節(jié)點(diǎn)是否已經(jīng)移動(dòng)
let maxNewIndexSoFar = 0

// works as Map<newIndex, oldIndex>
// 作新舊節(jié)點(diǎn)的下標(biāo)映射
// Note that oldIndex is offset by +1
// 注意 舊節(jié)點(diǎn)的 index 要向右偏移一個(gè)下標(biāo)

// and oldIndex = 0 is a special value indicating the new node has
// no corresponding old node.
// 并且舊節(jié)點(diǎn)Index = 0 是一個(gè)特殊的值,用于表示新的節(jié)點(diǎn)中沒(méi)有對(duì)應(yīng)的舊節(jié)點(diǎn)

// used for determining longest stable subsequence
// newIndexToOldIndexMap 用于確定最長(zhǎng)遞增子序列
// 新下標(biāo)與舊下標(biāo)的map
const newIndexToOldIndexMap = new Array(toBePatched)
// 將所有的值初始化為0
// [0, 0, 0, 0]
for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0
  • 變量 patched 用于記錄已經(jīng)patch的節(jié)點(diǎn)

  • 變量 toBePatched 用于記錄需要進(jìn)行patch的節(jié)點(diǎn)個(gè)數(shù)

  • 變量 moved 用于記錄是否有可復(fù)用節(jié)點(diǎn)發(fā)生交叉

  • maxNewIndexSoFar用于記錄當(dāng)舊的子序列中存在沒(méi)有設(shè)置key的子節(jié)點(diǎn),但是該子節(jié)點(diǎn)出現(xiàn)于新的子序列中,且可復(fù)用,最大下標(biāo)。

  • 變量newIndexToOldIndexMap用于映射新的子序列中的節(jié)點(diǎn)下標(biāo)對(duì)應(yīng)于 舊的子序列中的節(jié)點(diǎn)的下標(biāo)

  • 并且會(huì)將newIndexToOldIndexMap初始化為一個(gè)全0數(shù)組,[0, 0, 0, 0]

怎么深入解析Vue3中的diff 算法

知道了這些變量的含義之后 我們就可以開(kāi)始從左向右遍歷子序列了。

遍歷的時(shí)候,需要首先遍歷舊子序列,起點(diǎn)是s1,終點(diǎn)是e1。

遍歷的過(guò)程中會(huì)對(duì)patched進(jìn)行累加。

卸載舊節(jié)點(diǎn)

如果patched >= toBePatched,說(shuō)明新子序列中的子節(jié)點(diǎn)少于舊子序列中的節(jié)點(diǎn)數(shù)量。

需要對(duì)舊子節(jié)點(diǎn)進(jìn)行卸載。

// 遍歷未處理舊序列中子節(jié)點(diǎn)
for (i = s1; i <= e1; i++) {
    // 獲取舊節(jié)點(diǎn)
    // 會(huì)逐個(gè)獲取 c d e
    const prevChild = c1[i]
    // 如果已經(jīng)patch 的數(shù)量 >= 需要進(jìn)行patch的節(jié)點(diǎn)個(gè)數(shù)
    
    // patched剛開(kāi)始為 0
    // patched >= 4
    if (patched >= toBePatched) {
      // all new children have been patched so this can only be a removal
      // 這說(shuō)明所有的新節(jié)點(diǎn)已經(jīng)被patch 因此可以移除舊的
      unmount(prevChild, parentComponent, parentSuspense, true)
      continue
    }
}

如果prevChild.key是存在的,會(huì)通過(guò)前面我們構(gòu)建的keyToNewIndexMap,獲取prevChild在新子序列中的下標(biāo)newIndex。

獲取newIndex

// 新節(jié)點(diǎn)下標(biāo)
let newIndex
if (prevChild.key != null) {
  // 舊的節(jié)點(diǎn)肯定有key, 
  // 根據(jù)舊節(jié)點(diǎn)key  獲取相同類(lèi)型的新的子節(jié)點(diǎn)  在 新的隊(duì)列中對(duì)應(yīng)節(jié)點(diǎn)位置
  // 這個(gè)時(shí)候 因?yàn)閏 d e 是原來(lái)的節(jié)點(diǎn) 并且有key
  // h 是新增節(jié)點(diǎn) 舊節(jié)點(diǎn)中沒(méi)有 獲取不到 對(duì)應(yīng)的index 會(huì)走else
  // 所以newIndex在開(kāi)始時(shí)會(huì)有如下情況
  /**
   * node  newIndex
   *  c       4
   *  d       3
   *  e       2
   * */ 
  // 這里是可以獲取到newIndex的
  newIndex = keyToNewIndexMap.get(prevChild.key)
}

以圖為例,可以知道,在遍歷過(guò)程中,節(jié)點(diǎn)c、d、e為可復(fù)用節(jié)點(diǎn),分別對(duì)應(yīng)新子序列中的2、3、4的位置。

newIndex可以取到的值為4、3、2。

如果舊節(jié)點(diǎn)沒(méi)有key怎么辦?

// key-less node, try to locate a key-less node of the same type
// 如果舊的節(jié)點(diǎn)沒(méi)有key
// 則會(huì)查找沒(méi)有key的 且為相同類(lèi)型的新節(jié)點(diǎn)在 新節(jié)點(diǎn)隊(duì)列中 的位置
// j = 2: j <= 5
for (j = s2; j <= e2; j++) {
  if (
    newIndexToOldIndexMap[j - s2] === 0 &&
    // 判斷是否是新舊節(jié)點(diǎn)是否相同
    isSameVNodeType(prevChild, c2[j])
  ) {
    // 獲取到相同類(lèi)型節(jié)點(diǎn)的下標(biāo)
    newIndex = j
    break
  }
}

如果節(jié)點(diǎn)沒(méi)有key,則同樣會(huì)取新子序列中,遍歷查找沒(méi)有key且兩個(gè)新舊類(lèi)型相同子節(jié)點(diǎn),并以此節(jié)點(diǎn)的下標(biāo),作為newIndex

newIndexToOldIndexMap[j - s2] === 0 說(shuō)明節(jié)點(diǎn)沒(méi)有該節(jié)點(diǎn)沒(méi)有key。

如果還沒(méi)有獲取到newIndex,說(shuō)明在新子序列中沒(méi)有存在的與 prevChild 相同的子節(jié)點(diǎn),需要對(duì)prevChild進(jìn)行卸載。

if (newIndex === undefined) {
  // 沒(méi)有對(duì)應(yīng)的新節(jié)點(diǎn) 卸載舊的
  unmount(prevChild, parentComponent, parentSuspense, true)
}

否則,開(kāi)始根據(jù)newIndex,構(gòu)建keyToNewIndexMap,明確新舊節(jié)點(diǎn)對(duì)應(yīng)的下標(biāo)位置。

時(shí)刻牢記newIndex是根據(jù)舊節(jié)點(diǎn)獲取的其在新的子序列中的下標(biāo)。

// 這里處理獲取到newIndex的情況
// 開(kāi)始整理新節(jié)點(diǎn)下標(biāo) Index 對(duì)于 相同類(lèi)型舊節(jié)點(diǎn)在 舊隊(duì)列中的映射
// 新節(jié)點(diǎn)下標(biāo)從 s2=2 開(kāi)始,對(duì)應(yīng)的舊節(jié)點(diǎn)下標(biāo)需要偏移一個(gè)下標(biāo)
// 0 表示當(dāng)前節(jié)點(diǎn)沒(méi)有對(duì)應(yīng)的舊節(jié)點(diǎn)
// 偏移 1個(gè)位置 i從 s1 = 2 開(kāi)始,s2 = 2
// 4 - 2 獲取下標(biāo) 2,新的 c 節(jié)點(diǎn)對(duì)應(yīng)舊 c 節(jié)點(diǎn)的位置下標(biāo) 3
// 3 - 2 獲取下標(biāo) 1,新的 d 節(jié)點(diǎn)對(duì)應(yīng)舊 d 節(jié)點(diǎn)的位置下標(biāo) 4
// 2 - 2 獲取下標(biāo) 0,新的 e 節(jié)點(diǎn)對(duì)應(yīng)舊 e 節(jié)點(diǎn)的位置下標(biāo) 5
// [0, 0, 0, 0] => [5, 4, 3, 0]
// [2,3,4,5] = [5, 4, 3, 0]
newIndexToOldIndexMap[newIndex - s2] = i + 1
// newIndex 會(huì)取 4 3 2
/** 
 *   newIndex  maxNewIndexSoFar   moved
 *       4            0          false
 *       3            4           true
 *       2        
 * 
 * */ 
if (newIndex >= maxNewIndexSoFar) {
  maxNewIndexSoFar = newIndex
} else {
  moved = true
}

在構(gòu)建newIndexToOldIndexMap的同時(shí),會(huì)通過(guò)判斷newIndex、maxNewIndexSoFa的關(guān)系,確定節(jié)點(diǎn)是否發(fā)生移動(dòng)。

newIndexToOldIndexMap最后遍歷結(jié)束應(yīng)該為[5, 4, 3, 0]0說(shuō)明有舊序列中沒(méi)有與心序列中對(duì)應(yīng)的節(jié)點(diǎn),并且該節(jié)點(diǎn)可能是新增節(jié)點(diǎn)。

如果新舊節(jié)點(diǎn)在序列中相對(duì)位置保持始終不變,則maxNewIndexSoFar會(huì)隨著newIndex的遞增而遞增。

意味著節(jié)點(diǎn)沒(méi)有發(fā)生交叉。也就不需要移動(dòng)可復(fù)用節(jié)點(diǎn)。

否則可復(fù)用節(jié)點(diǎn)發(fā)生了移動(dòng),需要對(duì)可復(fù)用節(jié)點(diǎn)進(jìn)行move

遍歷的最后,會(huì)對(duì)新舊節(jié)點(diǎn)進(jìn)行patch,并對(duì)patched進(jìn)行累加,記錄已經(jīng)處理過(guò)幾個(gè)節(jié)點(diǎn)。

// 進(jìn)行遞歸patch
/**
 * old   new
 *  c     c
 *  d     d
 *  e     e 
*/
patch(
  prevChild,
  c2[newIndex],
  container,
  null,
  parentComponent,
  parentSuspense,
  isSVG,
  slotScopeIds,
  optimized
)
// 已經(jīng)patch的
patched++

經(jīng)過(guò)上面的處理,已經(jīng)完成對(duì)舊節(jié)點(diǎn)進(jìn)行了卸載,對(duì)相對(duì)位置保持沒(méi)有變化的子節(jié)點(diǎn)進(jìn)行了patch復(fù)用。

接下來(lái)就是需要移動(dòng)可復(fù)用節(jié)點(diǎn),掛載新子序列中新增節(jié)點(diǎn)。

2.5.3 移動(dòng)可復(fù)用節(jié)點(diǎn),掛載新增節(jié)點(diǎn)

這里涉及到一塊比較核心的代碼,也是Vue3 diff效率提升的關(guān)鍵所在。

前面通過(guò)newIndexToOldIndexMap,記錄了新舊子節(jié)點(diǎn)變化前后的下標(biāo)映射。

這里會(huì)通過(guò)getSequence方法獲取一個(gè)最大遞增子序列。用于記錄相對(duì)位置沒(méi)有發(fā)生變化的子節(jié)點(diǎn)的下標(biāo)。

根據(jù)此遞增子序列,可以實(shí)現(xiàn)在移動(dòng)可復(fù)用節(jié)點(diǎn)的時(shí)候,只移動(dòng)相對(duì)位置前后發(fā)生變化的子節(jié)點(diǎn)。

做到最小改動(dòng)。

那什么是最大遞增子序列?

  • 子序列是由數(shù)組派生而來(lái)的序列,刪除(或不刪除)數(shù)組中的元素而不改變其余元素的順序。

  • 而遞增子序列,是數(shù)組派生的子序列,各元素之間保持逐個(gè)遞增的關(guān)系。

  • 例如:

  • 數(shù)組[3, 6, 2, 7] 是數(shù)組 [0, 3, 1, 6, 2, 2, 7] 的最長(zhǎng)嚴(yán)格遞增子序列。

  • 數(shù)組[2, 3, 7, 101] 是數(shù)組 [10 , 9, 2, 5, 3, 7, 101, 18]的最大遞增子序列。

  • 數(shù)組[0, 1, 2, 3] 是數(shù)組 [0, 1, 0, 3, 2, 3]的最大遞增子序列。

怎么深入解析Vue3中的diff 算法

已上圖為例,在未處理的亂序節(jié)點(diǎn)中,存在新增節(jié)點(diǎn)N、I、需要卸載的節(jié)點(diǎn)G,及可復(fù)用節(jié)點(diǎn)C、D、E、F。

節(jié)點(diǎn)CDE在新舊子序列中相對(duì)位置沒(méi)有變換,如果想要通過(guò)最小變動(dòng)實(shí)現(xiàn)節(jié)點(diǎn)復(fù)用,我們可以將找出F節(jié)點(diǎn)變化前后的下標(biāo)位置,在新的子序列C節(jié)點(diǎn)之前插入F節(jié)點(diǎn)即可。

最大遞增子序列的作用就是通過(guò)新舊節(jié)點(diǎn)變化前后的映射,創(chuàng)建一個(gè)遞增數(shù)組,這樣就可以知道哪些節(jié)點(diǎn)在變化前后相對(duì)位置沒(méi)有發(fā)生變化,哪些節(jié)點(diǎn)需要進(jìn)行移動(dòng)。

Vue3中的遞增子序列的不同在于,它保存的是可復(fù)用節(jié)點(diǎn)在 newIndexToOldIndexMap的下標(biāo)。而并不是newIndexToOldIndexMap中的元素。

接下來(lái)我們看下代碼部分:

// 5.3 move and mount
// generate longest stable subsequence only when nodes have moved
// 移動(dòng)節(jié)點(diǎn) 掛載節(jié)點(diǎn)
// 僅當(dāng)節(jié)點(diǎn)被移動(dòng)后 生成最長(zhǎng)遞增子序列
// 經(jīng)過(guò)上面操作后,newIndexToOldIndexMap = [5, 4, 3, 0]
// 得到 increasingNewIndexSequence = [2]
const increasingNewIndexSequence = moved
  ? getSequence(newIndexToOldIndexMap)
  : EMPTY_ARR
// j = 0
j = increasingNewIndexSequence.length - 1
// looping backwards so that we can use last patched node as anchor
// 從后向前遍歷 以便于可以用最新的被patch的節(jié)點(diǎn)作為錨點(diǎn)
// i = 3
for (i = toBePatched - 1; i >= 0; i--) {
  // 5 4 3 2
  const nextIndex = s2 + i
  // 節(jié)點(diǎn) h  c  d  e 
  const nextChild = c2[nextIndex]
  // 獲取錨點(diǎn)
  const anchor =
    nextIndex + 1 < l2 ? c2[nextIndex + 1].el : parentAnchor
  // [5, 4, 3, 0] 節(jié)點(diǎn)h會(huì)被patch,其實(shí)是mount
  //  c  d  e 會(huì)被移動(dòng)
  if (newIndexToOldIndexMap[i] === 0) {
    // mount new
    // 掛載新的
    patch(
      null,
      nextChild,
      container,
      anchor,
      ...
    )
  } else if (moved) {
    // move if:
    // There is no stable subsequence (e.g. a reverse)
    // OR current node is not among the stable sequence
    // 如果沒(méi)有最長(zhǎng)遞增子序列或者 當(dāng)前節(jié)點(diǎn)不在遞增子序列中間
    // 則移動(dòng)節(jié)點(diǎn)
    // 
    if (j < 0 || i !== increasingNewIndexSequence[j]) {
      move(nextChild, container, anchor, MoveType.REORDER)
    } else {
      j--
    }
  }
}

怎么深入解析Vue3中的diff 算法

從上面的代碼可以知道:

  • 通過(guò)newIndexToOldIndexMap獲取的最大遞增子序列是[2]

  • j = 0

  • 遍歷的時(shí)候從右向左遍歷,這樣可以獲取到錨點(diǎn),如果有已經(jīng)經(jīng)過(guò)patch的兄弟節(jié)點(diǎn),則以兄弟節(jié)點(diǎn)作為錨點(diǎn),否則以父節(jié)點(diǎn)作為錨點(diǎn)

  • newIndexToOldIndexMap[i] === 0,說(shuō)明是新增節(jié)點(diǎn)。需要對(duì)節(jié)點(diǎn)進(jìn)行mount,這時(shí)只需給patch的第一個(gè)參數(shù)傳null即可。可以知道首先會(huì)對(duì)h節(jié)點(diǎn)進(jìn)行patch。

  • 否則會(huì)判斷moved是否為true。通過(guò)前面的分析,我們知道節(jié)點(diǎn)C & 節(jié)點(diǎn)E在前后變化中發(fā)生了位置移動(dòng)。

  • 故這里會(huì)移動(dòng)節(jié)點(diǎn),我們知道 j此時(shí)為0,i此時(shí)為**2**,i !== increasingNewIndexSequence[j]true,并不會(huì)移動(dòng)C節(jié)點(diǎn),而是執(zhí)行 j--

  • 后面因?yàn)?j < 0,會(huì)對(duì) D、E節(jié)點(diǎn)進(jìn)行移動(dòng)。

至此我們就完成了Vue3 diff算法的學(xué)習(xí)分析。

這里為大家提供了一個(gè)示例,可以結(jié)合本文的分析過(guò)程進(jìn)行練習(xí):

可以只看第一張圖進(jìn)行分析,分析結(jié)束后可以與第二三張圖片進(jìn)行對(duì)比。

圖一:

怎么深入解析Vue3中的diff 算法

圖二 & 三:

怎么深入解析Vue3中的diff 算法

怎么深入解析Vue3中的diff 算法

vue是什么

Vue是一套用于構(gòu)建用戶(hù)界面的漸進(jìn)式JavaScript框架,Vue與其它大型框架的區(qū)別是,使用Vue可以自底向上逐層應(yīng)用,其核心庫(kù)只關(guān)注視圖層,方便與第三方庫(kù)和項(xiàng)目整合,且使用Vue可以采用單文件組件和Vue生態(tài)系統(tǒng)支持的庫(kù)開(kāi)發(fā)復(fù)雜的單頁(yè)應(yīng)用。

以上就是怎么深入解析Vue3中的diff 算法的全部?jī)?nèi)容了,更多與怎么深入解析Vue3中的diff 算法相關(guān)的內(nèi)容可以搜索創(chuàng)新互聯(lián)之前的文章或者瀏覽下面的文章進(jìn)行學(xué)習(xí)哈!相信小編會(huì)給大家增添更多知識(shí),希望大家能夠支持一下創(chuàng)新互聯(lián)!

標(biāo)題名稱(chēng):怎么深入解析Vue3中的diff算法
新聞來(lái)源:http://bm7419.com/article42/jdjcec.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供動(dòng)態(tài)網(wǎng)站、網(wǎng)站建設(shè)企業(yè)網(wǎng)站制作、移動(dòng)網(wǎng)站建設(shè)網(wǎng)站營(yíng)銷(xiāo)、建站公司

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

搜索引擎優(yōu)化