欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解vue3.0 diff算法的使用(超詳細(xì))

 更新時(shí)間:2020年07月01日 11:05:15   作者:alien  
這篇文章主要介紹了詳解vue3.0 diff算法的使用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

前言:隨之vue3.0beta版本的發(fā)布,vue3.0正式版本相信不久就會(huì)與我們相遇。尤玉溪在直播中也說了vue3.0的新特性typescript強(qiáng)烈支持,proxy響應(yīng)式原理,重新虛擬dom,優(yōu)化diff算法性能提升等等。小編在這里仔細(xì)研究了vue3.0beta版本diff算法的源碼,并希望把其中的細(xì)節(jié)和奧妙和大家一起分享。

首先我們來(lái)思考一些大中廠面試中,很容易問到的問題:

1 什么時(shí)候用到diff算法,diff算法作用域在哪里?

2 diff算法是怎么運(yùn)作的,到底有什么作用?

3 在v-for 循環(huán)列表 key 的作用是什么

4 用索引index做key真的有用? 到底用什么做key才是最佳方案。

如果遇到這些問題,大家是怎么回答的呢?我相信當(dāng)你讀完這篇文章,這些問題也會(huì)迎刃而解。

一 什么時(shí)候用到了diff算法,diff算法作用域?

 1.1diff算法的作用域

 patch概念引入

在vue update過程中在遍歷子代vnode的過程中,會(huì)用不同的patch方法來(lái)patch新老vnode,如果找到對(duì)應(yīng)的 newVnode 和 oldVnode,就可以復(fù)用利用里面的真實(shí)dom節(jié)點(diǎn)。避免了重復(fù)創(chuàng)建元素帶來(lái)的性能開銷。畢竟瀏覽器創(chuàng)造真實(shí)的dom,操縱真實(shí)的dom,性能代價(jià)是昂貴的。

patch過程中,如果面對(duì)當(dāng)前vnode存在有很多chidren的情況,那么需要分別遍歷patch新的children Vnode和老的 children vnode。

存在chidren的vnode類型

首先思考一下什么類型的vnode會(huì)存在children。

①element元素類型vnode

第一中情況就是element類型vnode 會(huì)存在 children vode,此時(shí)的三個(gè)span標(biāo)簽就是chidren vnode情況

<div>
  <span> 蘋果🍎 </span> 
  <span> 香蕉🍌 </span>
  <span> 鴨梨🍐 </span>
</div>

在vue3.0源碼中 ,patchElement用于處理element類型的vnode ②flagment碎片類型vnode

在Vue3.0中,引入了一個(gè)fragment碎片概念。

你可能會(huì)問,什么是碎片?如果你創(chuàng)建一個(gè)Vue組件,那么它只能有一個(gè)根節(jié)點(diǎn)。

<template>
  <span> 蘋果🍎 </span> 
  <span> 香蕉🍌 </span>
  <span> 鴨梨🍐 </span>
</template>

這樣可能會(huì)報(bào)出警告,原因是代表任何Vue組件的Vue實(shí)例需要綁定到一個(gè)單一的DOM元素中。唯一可以創(chuàng)建一個(gè)具有多個(gè)DOM節(jié)點(diǎn)的組件的方法就是創(chuàng)建一個(gè)沒有底層Vue實(shí)例的功能組件。

flagment出現(xiàn)就是用看起來(lái)像一個(gè)普通的DOM元素,但它是虛擬的,根本不會(huì)在DOM樹中呈現(xiàn)。這樣我們可以將組件功能綁定到一個(gè)單一的元素中,而不需要?jiǎng)?chuàng)建一個(gè)多余的DOM節(jié)點(diǎn)。

 <Fragment>
  <span> 蘋果🍎 </span> 
  <span> 香蕉🍌 </span>
  <span> 鴨梨🍐 </span>
</Fragment>

在vue3.0源碼中 ,processFragment用于處理Fragment類型的vnode

 1.2 patchChildren

從上文中我們得知了存在children的vnode類型,那么存在children就需要patch每一個(gè)

children vnode依次向下遍歷。那么就需要一個(gè)patchChildren方法,依次patch子類vnode。

patchChildren

vue3.0中 在patchChildren方法中有這么一段源碼

if (patchFlag > 0) {
   if (patchFlag & PatchFlags.KEYED_FRAGMENT) { 
     /* 對(duì)于存在key的情況用于diff算法 */
    patchKeyedChildren(
     c1 as VNode[],
     c2 as VNodeArrayChildren,
     container,
     anchor,
     parentComponent,
     parentSuspense,
     isSVG,
     optimized
    )
    return
   } else if (patchFlag & PatchFlags.UNKEYED_FRAGMENT) {
     /* 對(duì)于不存在key的情況,直接patch */
    patchUnkeyedChildren( 
     c1 as VNode[],
     c2 as VNodeArrayChildren,
     container,
     anchor,
     parentComponent,
     parentSuspense,
     isSVG,
     optimized
    )
    return
   }
  }

patchChildren根據(jù)是否存在key進(jìn)行真正的diff或者直接patch。 既然diff算法存在patchChildren方法中,而patchChildren方法用在Fragment類型和element類型的vnode中,這樣也就解釋了diff算法的作用域是什么。 1.3 diff算法作用?

通過前言我們知道,存在這children的情況的vnode,需要通過patchChildren遍歷children依次進(jìn)行patch操作,如果在patch期間,再發(fā)現(xiàn)存在vnode情況,那么會(huì)遞歸的方式依次向下patch,那么找到與新的vnode對(duì)應(yīng)的vnode顯的如此重要。

我們用兩幅圖來(lái)向大家展示vnode變化。

如上兩幅圖表示在一次更新中新老dom樹變化情況。

假設(shè)不存在diff算法,依次按照先后順序patch會(huì)發(fā)生什么

如果 不存在diff算法 ,而是直接patchchildren 就會(huì)出現(xiàn)如下圖的邏輯。

第一次patchChidren

第二次patchChidren

第三次patchChidren‘

第四次patchChidren

如果沒有用到diff算法,而是依次patch虛擬dom樹,那么如上稍微 修改dom順序 ,就會(huì)在patch過程中沒有一對(duì)正確的新老vnode,所以老vnode的節(jié)點(diǎn)沒有一個(gè)可以復(fù)用,這樣就需要重新創(chuàng)造新的節(jié)點(diǎn),浪費(fèi)了性能開銷,這顯然不是我們需要的。

那么diff算法的作用就來(lái)了。

diff作用就是在patch子vnode過程中,找到與新vnode對(duì)應(yīng)的老vnode,復(fù)用真實(shí)的dom節(jié)點(diǎn),避免不必要的性能開銷

二 diff算法具體做了什么(重點(diǎn))?

在正式講diff算法之前,在patchChildren的過程中,存在 patchKeyedChildren

patchUnkeyedChildren

patchKeyedChildren 是正式的開啟diff的流程,那么patchUnkeyedChildren的作用是什么呢? 我們來(lái)看看針對(duì)沒有key的情況patchUnkeyedChildren會(huì)做什么。

c1 = c1 || EMPTY_ARR
  c2 = c2 || EMPTY_ARR
  const oldLength = c1.length
  const newLength = c2.length
  const commonLength = Math.min(oldLength, newLength)
  let i
  for (i = 0; i < commonLength; i++) { /* 依次遍歷新老vnode進(jìn)行patch */
   const nextChild = (c2[i] = optimized
    ? cloneIfMounted(c2[i] as VNode)
    : normalizeVNode(c2[i]))
   patch(
    c1[i],
    nextChild,
    container,
    null,
    parentComponent,
    parentSuspense,
    isSVG,
    optimized
   )
  }
  if (oldLength > newLength) { /* 老vnode 數(shù)量大于新的vnode,刪除多余的節(jié)點(diǎn) */
   unmountChildren(c1, parentComponent, parentSuspense, true, commonLength)
  } else { /* /* 老vnode 數(shù)量小于于新的vnode,創(chuàng)造新的即誒安 */
   mountChildren(
    c2,
    container,
    anchor,
    parentComponent,
    parentSuspense,
    isSVG,
    optimized,
    commonLength
   )
  }

我們可以得到結(jié)論,對(duì)于不存在key情況

① 比較新老children的length獲取最小值 然后對(duì)于公共部分,進(jìn)行從新patch工作。

② 如果老節(jié)點(diǎn)數(shù)量大于新的節(jié)點(diǎn)數(shù)量 ,移除多出來(lái)的節(jié)點(diǎn)。

③ 如果新的節(jié)點(diǎn)數(shù)量大于老節(jié)點(diǎn)的數(shù)量,從新 mountChildren新增的節(jié)點(diǎn)。

那么對(duì)于存在key情況呢? 會(huì)用到diff算法 , diff算法做了什么呢?

patchKeyedChildren方法究竟做了什么?

我們先來(lái)看看一些聲明的變量。

  /* c1 老的vnode c2 新的vnode */
  let i = 0       /* 記錄索引 */
  const l2 = c2.length  /* 新vnode的數(shù)量 */
  let e1 = c1.length - 1 /* 老vnode 最后一個(gè)節(jié)點(diǎn)的索引 */
  let e2 = l2 - 1    /* 新節(jié)點(diǎn)最后一個(gè)節(jié)點(diǎn)的索引 */

①第一步從頭開始向尾尋找

(a b) c

(a b) d e

 /* 從頭對(duì)比找到有相同的節(jié)點(diǎn) patch ,發(fā)現(xiàn)不同,立即跳出*/
  while (i <= e1 && i <= e2) {
   const n1 = c1[i]
   const n2 = (c2[i] = optimized
    ? cloneIfMounted(c2[i] as VNode)
    : normalizeVNode(c2[i]))
    /* 判斷key ,type是否相等 */
   if (isSameVNodeType(n1, n2)) {
    patch(
     n1,
     n2,
     container, 
     parentAnchor,
     parentComponent,
     parentSuspense,
     isSVG,
     optimized
    )
   } else {
    break
   }
   i++
  }

第一步的事情就是從頭開始尋找相同的vnode,然后進(jìn)行patch,如果發(fā)現(xiàn)不是相同的節(jié)點(diǎn),那么立即跳出循環(huán)。

具體流程如圖所示

isSameVNodeType

export function isSameVNodeType(n1: VNode, n2: VNode): boolean {
 return n1.type === n2.type && n1.key === n2.key
}

isSameVNodeType 作用就是判斷當(dāng)前vnode類型 和 vnode的 key是否相等

②第二步從尾開始同前diff

a (b c)

d e (b c)

/* 如果第一步?jīng)]有patch完,立即,從后往前開始patch ,如果發(fā)現(xiàn)不同立即跳出循環(huán) */
  while (i <= e1 && i <= e2) {
   const n1 = c1[e1]
   const n2 = (c2[e2] = optimized
    ? cloneIfMounted(c2[e2] as VNode)
    : normalizeVNode(c2[e2]))
   if (isSameVNodeType(n1, n2)) {
    patch(
     n1,
     n2,
     container,
     parentAnchor,
     parentComponent,
     parentSuspense,
     isSVG,
     optimized
    )
   } else {
    break
   }
   e1--
   e2--
  }

經(jīng)歷第一步操作之后,如果發(fā)現(xiàn)沒有patch完,那么立即進(jìn)行第二部,從尾部開始遍歷依次向前diff。

如果發(fā)現(xiàn)不是相同的節(jié)點(diǎn),那么立即跳出循環(huán)。

具體流程如圖所示

③④主要針對(duì)新增和刪除元素的情況,前提是元素沒有發(fā)生移動(dòng), 如果有元素發(fā)生移動(dòng)就要走⑤邏輯。 ③ 如果老節(jié)點(diǎn)是否全部patch,新節(jié)點(diǎn)沒有被patch完,創(chuàng)建新的vnode

(a b)

(a b) c

i = 2, e1 = 1, e2 = 2

(a b)

c (a b)

i = 0, e1 = -1, e2 = 0

/* 如果新的節(jié)點(diǎn)大于老的節(jié)點(diǎn)數(shù) ,對(duì)于剩下的節(jié)點(diǎn)全部以新的vnode處理( 這種情況說明已經(jīng)patch完相同的vnode ) */
  if (i > e1) {
   if (i <= e2) {
    const nextPos = e2 + 1
    const anchor = nextPos < l2 ? (c2[nextPos] as VNode).el : parentAnchor
    while (i <= e2) {
     patch( /* 創(chuàng)建新的節(jié)點(diǎn)*/
      null,
      (c2[i] = optimized
       ? cloneIfMounted(c2[i] as VNode)
       : normalizeVNode(c2[i])),
      container,
      anchor,
      parentComponent,
      parentSuspense,
      isSVG
     )
     i++
    }
   }
  }

i > e1

如果新的節(jié)點(diǎn)大于老的節(jié)點(diǎn)數(shù) ,對(duì)于剩下的節(jié)點(diǎn)全部以新的vnode處理( 這種情況說明已經(jīng)patch完相同的vnode ),也就是要全部create新的vnode.

具體邏輯如圖所示

④ 如果新節(jié)點(diǎn)全部被patch,老節(jié)點(diǎn)有剩余,那么卸載所有老節(jié)點(diǎn)

i > e2

(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++
  }
}

對(duì)于老的節(jié)點(diǎn)大于新的節(jié)點(diǎn)的情況 ,對(duì)于超出的節(jié)點(diǎn)全部卸載 ( 這種情況說明已經(jīng)patch完相同的vnode )

具體邏輯如圖所示

⑤ 不確定的元素 ( 這種情況說明沒有patch完相同的vnode ),我們可以接著①②的邏輯繼續(xù)往下看 diff核心

在①②情況下沒有遍歷完的節(jié)點(diǎn)如下圖所示。

剩下的節(jié)點(diǎn)。

 const s1 = i //第一步遍歷到的index
   const s2 = i 
   const keyToNewIndexMap: Map<string | number, number> = new Map()
   /* 把沒有比較過的新的vnode節(jié)點(diǎn),通過map保存 */
   for (i = s2; i <= e2; i++) {
    if (nextChild.key != null) {
     keyToNewIndexMap.set(nextChild.key, i)
    }
   }
   let j
   let patched = 0 
   const toBePatched = e2 - s2 + 1 /* 沒有經(jīng)過 path 新的節(jié)點(diǎn)的數(shù)量 */
   let moved = false /* 證明是否 */
   let maxNewIndexSoFar = 0 
   const newIndexToOldIndexMap = new Array(toBePatched)
    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0
   /* 建立一個(gè)數(shù)組,每個(gè)子元素都是0 [ 0, 0, 0, 0, 0, 0, ] */ 

遍歷所有新節(jié)點(diǎn)把索引和對(duì)應(yīng)的key,存入map keyToNewIndexMap中

keyToNewIndexMap存放 key -> index 的map

D : 2

E : 3

C : 4

I : 5

接下來(lái)聲明一個(gè)新的指針 j ,記錄剩下新的節(jié)點(diǎn)的索引。

patched,記錄在第⑤步patched新節(jié)點(diǎn)過的數(shù)量

toBePatched記錄⑤步之前,沒有經(jīng)過patched 新的節(jié)點(diǎn)的數(shù)量。

moved代表是否發(fā)生過移動(dòng),咱們的demo是已經(jīng)發(fā)生過移動(dòng)的。

newIndexToOldIndexMap用來(lái)存放新節(jié)點(diǎn)索引和老節(jié)點(diǎn)索引的數(shù)組。

newIndexToOldIndexMap 數(shù)組的index是新vnode的索引 , value是老vnode的索引。

接下來(lái)

 for (i = s1; i <= e1; i++) { /* 開始遍歷老節(jié)點(diǎn) */
    const prevChild = c1[i]
    if (patched >= toBePatched) { /* 已經(jīng)patch數(shù)量大于等于, */
     /* ① 如果 toBePatched新的節(jié)點(diǎn)數(shù)量為0 ,那么統(tǒng)一卸載老的節(jié)點(diǎn) */
     unmount(prevChild, parentComponent, parentSuspense, true)
     continue
    }
    let newIndex
     /* ② 如果,老節(jié)點(diǎn)的key存在 ,通過key找到對(duì)應(yīng)的index */
    if (prevChild.key != null) {
     newIndex = keyToNewIndexMap.get(prevChild.key)
    } else { /* ③ 如果,老節(jié)點(diǎn)的key不存在 */
     for (j = s2; j <= e2; j++) { /* 遍歷剩下的所有新節(jié)點(diǎn) */
      if (
       newIndexToOldIndexMap[j - s2] === 0 && /* newIndexToOldIndexMap[j - s2] === 0 新節(jié)點(diǎn)沒有被patch */
       isSameVNodeType(prevChild, c2[j] as VNode)
      ) { /* 如果找到與當(dāng)前老節(jié)點(diǎn)對(duì)應(yīng)的新節(jié)點(diǎn)那么 ,將新節(jié)點(diǎn)的索引,賦值給newIndex */
       newIndex = j
       break
      }
     }
    }
    if (newIndex === undefined) { /* ①?zèng)]有找到與老節(jié)點(diǎn)對(duì)應(yīng)的新節(jié)點(diǎn),刪除當(dāng)前節(jié)點(diǎn),卸載所有的節(jié)點(diǎn) */
     unmount(prevChild, parentComponent, parentSuspense, true)
    } else {
     /* ②把老節(jié)點(diǎn)的索引,記錄在存放新節(jié)點(diǎn)的數(shù)組中, */
     newIndexToOldIndexMap[newIndex - s2] = i + 1
     if (newIndex >= maxNewIndexSoFar) {
      maxNewIndexSoFar = newIndex
     } else {
      /* 證明有節(jié)點(diǎn)已經(jīng)移動(dòng)了  */
      moved = true
     }
     /* 找到新的節(jié)點(diǎn)進(jìn)行patch節(jié)點(diǎn) */
     patch(
      prevChild,
      c2[newIndex] as VNode,
      container,
      null,
      parentComponent,
      parentSuspense,
      isSVG,
      optimized
     )
     patched++
    }
 }

這段代碼算是diff算法的核心。

第一步: 通過老節(jié)點(diǎn)的key找到對(duì)應(yīng)新節(jié)點(diǎn)的index:開始遍歷老的節(jié)點(diǎn),判斷有沒有key, 如果存在key通過新節(jié)點(diǎn)的keyToNewIndexMap找到與新節(jié)點(diǎn)index,如果不存在key那么會(huì)遍歷剩下來(lái)的新節(jié)點(diǎn)試圖找到對(duì)應(yīng)index。 第二步:如果存在index證明有對(duì)應(yīng)的老節(jié)點(diǎn),那么直接復(fù)用老節(jié)點(diǎn)進(jìn)行patch,沒有找到與老節(jié)點(diǎn)對(duì)應(yīng)的新節(jié)點(diǎn),刪除當(dāng)前老節(jié)點(diǎn)。 第三步:newIndexToOldIndexMap找到對(duì)應(yīng)新老節(jié)點(diǎn)關(guān)系。

到這里,我們patch了一遍,把所有的老vnode都patch了一遍。

如圖所示

但是接下來(lái)的問題。

1 雖然已經(jīng)patch過所有的老節(jié)點(diǎn)。可以對(duì)于已經(jīng)發(fā)生移動(dòng)的節(jié)點(diǎn),要怎么真正移動(dòng)dom元素。

2 對(duì)于新增的節(jié)點(diǎn),(圖中節(jié)點(diǎn)I)并沒有處理,應(yīng)該怎么處理。

  /*移動(dòng)老節(jié)點(diǎn)創(chuàng)建新節(jié)點(diǎn)*/
   /* 根據(jù)最長(zhǎng)穩(wěn)定序列移動(dòng)相對(duì)應(yīng)的節(jié)點(diǎn) */
   const increasingNewIndexSequence = moved
    ? getSequence(newIndexToOldIndexMap)
    : EMPTY_ARR
   j = increasingNewIndexSequence.length - 1
   for (i = toBePatched - 1; i >= 0; i--) {
    const nextIndex = s2 + i
    const nextChild = c2[nextIndex] as VNode
    const anchor =
     nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor
    if (newIndexToOldIndexMap[i] === 0) { /* 沒有老的節(jié)點(diǎn)與新的節(jié)點(diǎn)對(duì)應(yīng),則創(chuàng)建一個(gè)新的vnode */
     patch(
      null,
      nextChild,
      container,
      anchor,
      parentComponent,
      parentSuspense,
      isSVG
     )
    } else if (moved) {
     if (j < 0 || i !== increasingNewIndexSequence[j]) { /*如果沒有在長(zhǎng)*/
      /* 需要移動(dòng)的vnode */
      move(nextChild, container, anchor, MoveType.REORDER)
     } else {
      j--
     }  

⑥最長(zhǎng)穩(wěn)定序列

首選通過getSequence得到一個(gè)最長(zhǎng)穩(wěn)定序列,對(duì)于index === 0 的情況也就是 新增節(jié)點(diǎn)(圖中I) 需要從新mount一個(gè)新的vnode,然后對(duì)于發(fā)生移動(dòng)的節(jié)點(diǎn)進(jìn)行統(tǒng)一的移動(dòng)操作

什么叫做最長(zhǎng)穩(wěn)定序列

對(duì)于以下的原始序列

0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15

最長(zhǎng)遞增子序列為

0, 2, 6, 9, 11, 15.

為什么要得到最長(zhǎng)穩(wěn)定序列

因?yàn)槲覀冃枰粋€(gè)序列作為基礎(chǔ)的參照序列,其他未在穩(wěn)定序列的節(jié)點(diǎn),進(jìn)行移動(dòng)。

總結(jié)

經(jīng)過上述我們大致知道了diff算法的流程

1 從頭對(duì)比找到有相同的節(jié)點(diǎn) patch ,發(fā)現(xiàn)不同,立即跳出。

2如果第一步?jīng)]有patch完,立即,從后往前開始patch ,如果發(fā)現(xiàn)不同立即跳出循環(huán)。 3如果新的節(jié)點(diǎn)大于老的節(jié)點(diǎn)數(shù) ,對(duì)于剩下的節(jié)點(diǎn)全部以新的vnode處理( 這種情況說明已經(jīng)patch完相同的vnode )。 4 對(duì)于老的節(jié)點(diǎn)大于新的節(jié)點(diǎn)的情況 , 對(duì)于超出的節(jié)點(diǎn)全部卸載 ( 這種情況說明已經(jīng)patch完相同的vnode )。 5不確定的元素( 這種情況說明沒有patch完相同的vnode ) 與 3 ,4對(duì)立關(guān)系。

1 把沒有比較過的新的vnode節(jié)點(diǎn),通過map保存

記錄已經(jīng)patch的新節(jié)點(diǎn)的數(shù)量 patched

沒有經(jīng)過 path 新的節(jié)點(diǎn)的數(shù)量 toBePatched

建立一個(gè)數(shù)組newIndexToOldIndexMap,每個(gè)子元素都是[ 0, 0, 0, 0, 0, 0, ] 里面的數(shù)字記錄老節(jié)點(diǎn)的索引 ,數(shù)組索引就是新節(jié)點(diǎn)的索引

開始遍歷老節(jié)點(diǎn)

① 如果 toBePatched新的節(jié)點(diǎn)數(shù)量為0 ,那么統(tǒng)一卸載老的節(jié)點(diǎn)

② 如果,老節(jié)點(diǎn)的key存在 ,通過key找到對(duì)應(yīng)的index

③ 如果,老節(jié)點(diǎn)的key不存在

1 遍歷剩下的所有新節(jié)點(diǎn)

2 如果找到與當(dāng)前老節(jié)點(diǎn)對(duì)應(yīng)的新節(jié)點(diǎn)那么 ,將新節(jié)點(diǎn)的索引,賦值給newIndex

④ 沒有找到與老節(jié)點(diǎn)對(duì)應(yīng)的新節(jié)點(diǎn),卸載當(dāng)前老節(jié)點(diǎn)。

⑤ 如果找到與老節(jié)點(diǎn)對(duì)應(yīng)的新節(jié)點(diǎn),把老節(jié)點(diǎn)的索引,記錄在存放新節(jié)點(diǎn)的數(shù)組中,

1 如果節(jié)點(diǎn)發(fā)生移動(dòng) 記錄已經(jīng)移動(dòng)了

2 patch新老節(jié)點(diǎn) 找到新的節(jié)點(diǎn)進(jìn)行patch節(jié)點(diǎn)

遍歷結(jié)束 如果發(fā)生移動(dòng)

① 根據(jù) newIndexToOldIndexMap 新老節(jié)點(diǎn)索引列表找到最長(zhǎng)穩(wěn)定序列

② 對(duì)于 newIndexToOldIndexMap -item =0 證明不存在老節(jié)點(diǎn) ,從新形成新的vnode

③ 對(duì)于發(fā)生移動(dòng)的節(jié)點(diǎn)進(jìn)行移動(dòng)處理。

三 key的作用,如何正確key。

1key的作用

在我們上述diff算法中,通過isSameVNodeType方法判斷,來(lái)判斷key是否相等判斷新老節(jié)點(diǎn)。

那么由此我們可以總結(jié)出?

在v-for循環(huán)中,key的作用是:通過判斷newVnode和OldVnode的key是否相等,從而復(fù)用與新節(jié)點(diǎn)對(duì)應(yīng)的老節(jié)點(diǎn),節(jié)約性能的開銷。

2如何正確使用key

①錯(cuò)誤用法 1:用index做key。

用index做key的效果實(shí)際和沒有用diff算法是一樣的,為什么這么說呢,下面我就用一幅圖來(lái)說明:

如果所示當(dāng)我們用index作為key的時(shí)候,無(wú)論我們?cè)趺礃右苿?dòng)刪除節(jié)點(diǎn),到了diff算法中都會(huì)從頭到尾依次patch(圖中: 所有節(jié)點(diǎn)均未有效的復(fù)用 )

②錯(cuò)誤用法2 :用index拼接其他值作為key。

當(dāng)已用index拼接其他值作為索引的時(shí)候,因?yàn)槊恳粋€(gè)節(jié)點(diǎn)都找不到對(duì)應(yīng)的key,導(dǎo)致所有的節(jié)點(diǎn)都不能復(fù)用,所有的新vnode都需要重新創(chuàng)建。都需要重新create

如圖所示。

③正確用法 :用唯一值id做key(我們可以用前后端交互的數(shù)據(jù)源的id為key)。

如圖所示。每一個(gè)節(jié)點(diǎn)都做到了復(fù)用。起到了diff算法的真正作用。

四 總結(jié)

我們?cè)谏厦?,已?jīng)把剛開始的問題統(tǒng)統(tǒng)解決了,最后用一張思維腦圖來(lái)從新整理一下整個(gè)流程。

到此這篇關(guān)于詳解vue3.0 diff算法的使用(超詳細(xì))的文章就介紹到這了,更多相關(guān)vue3.0 diff算法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論