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

詳解key在Vue列表渲染時(shí)究竟起到了什么作用

 更新時(shí)間:2019年04月20日 09:53:41   作者:LoneYin  
這篇文章主要介紹了key在Vue列表渲染時(shí)究竟起到了什么作用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

Vue2+采用diff算法來(lái)進(jìn)行新舊vnode的對(duì)比從而更新DOM節(jié)點(diǎn)。而通常在我們使用v-for這個(gè)指令的時(shí)候,Vue會(huì)要求你給循環(huán)列表的每一項(xiàng)添加唯一的key,那么這個(gè)key在渲染列表時(shí)究竟起到了什么作用呢?

在解釋這一點(diǎn)之前,你最好已經(jīng)了解Vue的diff算法的具體原理是什么。

Vue2更新真實(shí)DOM的操作主要是兩種:創(chuàng)建新DOM節(jié)點(diǎn)并移除舊DOM節(jié)點(diǎn)和更新已存在的DOM節(jié)點(diǎn),這兩種方式里創(chuàng)建新DOM節(jié)點(diǎn)的開銷肯定是遠(yuǎn)大于更新或移動(dòng)已有的DOM節(jié)點(diǎn),所以在diff中邏輯都是為了減少新的創(chuàng)建而更多的去復(fù)用已有DOM節(jié)點(diǎn)來(lái)完成DOM的更新。

在新舊vnode的diff過(guò)程中,key是判斷兩個(gè)節(jié)點(diǎn)是否為同一節(jié)點(diǎn)的首要條件:

// 參見Vue2源碼 core/vdom/patch.js

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)
      )
    )
  )
}


值得注意的是,如果新舊vnode的key值都未定義的話那么兩個(gè)key都為undefined,a.key === b.key 是成立的

接下來(lái)是在updateChildren方法中,這個(gè)方法會(huì)對(duì)新舊vnode進(jìn)行diff,然后將比對(duì)出的結(jié)果用來(lái)更新真實(shí)的DOM

// 參見Vue2源碼 core/vdom/patch.js

function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
  ...
  while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    if (isUndef(oldStartVnode)) {
      ...
    } else if (isUndef(oldEndVnode)) {
      ...
    } else if (sameVnode(oldStartVnode, newStartVnode)) {
      ...
    } else if (sameVnode(oldEndVnode, newEndVnode)) {
      ...
    } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
      ...
    } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
      ...
    } 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]
    }
  }
  ...
}

設(shè)置key的可以在diff中更快速的找到對(duì)應(yīng)節(jié)點(diǎn),提高diff速度

在updateChildren方法的while循環(huán)中,如果頭尾交叉對(duì)比沒(méi)有結(jié)果,即oldStartVnode存在且oldEndVnode存在且新舊children首尾四個(gè)vnode互不相同的條件下,會(huì)根據(jù)newStartVnode的key去對(duì)比oldCh數(shù)組中的key,從而找到相應(yīng)oldVnode

首先通過(guò)createKeyToOldIdx方法創(chuàng)建一個(gè)關(guān)于oldCh的map

if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)

function createKeyToOldIdx (children, beginIdx, endIdx) {
  let i, key
  const map = {}
  for (i = beginIdx; i <= endIdx; ++i) {
    key = children[i].key
    if (isDef(key)) map[key] = i
  }
  return map
}

這個(gè)map中將所有定義了key的oldVnode在數(shù)組中的index值作為鍵值,它的key作為鍵名存儲(chǔ)起來(lái),然后賦給oldKeyToIdx

idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)

function findIdxInOld (node, oldCh, start, end) {
  for (let i = start; i < end; i++) {
    const c = oldCh[i]
    if (isDef(c) && sameVnode(node, c)) return i
  }
}

如果newStartVnode的key存在的話,就去oldKeyToIdx中尋找相同key所對(duì)應(yīng)的index值,這樣就能拿到跟newStartVnode的key相同的oldVnode在oldCh數(shù)組中的index,即得到了與newStartVnode對(duì)應(yīng)的oldVnode。如果找不到的話,那么idxInOld就為undefined。

而如果newStartVnode并沒(méi)有設(shè)置key,則通過(guò)findIdxInOld方法遍歷oldCh來(lái)獲取與newStartVnode互為sameVnode的oldVnode,返回這個(gè)oldVnode在oldCh數(shù)組的index。(前面介紹過(guò),Vue在更新真實(shí)DOM時(shí)傾向于真實(shí)DOM節(jié)點(diǎn)的復(fù)用,所以在這里還是會(huì)選擇去找對(duì)應(yīng)的oldVnode,來(lái)更新已有的DOM節(jié)點(diǎn))

這時(shí)候設(shè)置key的好處就顯而易見了,有key存在時(shí)我們可以通過(guò)map映射快速定位到對(duì)應(yīng)的oldVnode然后進(jìn)行patch,沒(méi)有key值時(shí)我們需要遍歷這個(gè)oldCh數(shù)組然后去一一進(jìn)行比較,相比之下肯定是key存在時(shí)diff更高效。

接下來(lái)就是更新DOM的過(guò)程,如果oldCh[idxInOld]存在且與newStartVnode互為sameVnode存在則先更新再移動(dòng),否則創(chuàng)建新的element

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)
  }
}

那么設(shè)置key值就一定能提高diff效率嗎?

答案是否定的

`<div v-for="i in arr">{{ i }}</div>`

// 如果我們的數(shù)組是這樣的
[1, 2, 3, 4, 5]

// 它的渲染結(jié)果是這樣的
`<div>1</div>` // key: undefined
`<div>2</div>` // key: undefined
`<div>3</div>` // key: undefined
`<div>4</div>` // key: undefined
`<div>5</div>` // key: undefined

// 將它打亂
[4, 1, 3, 5, 2]

// 渲染結(jié)果是這樣的 期間只發(fā)生了DOM節(jié)點(diǎn)的文本內(nèi)容的更新
`<div>4</div>` // key: undefined
`<div>1</div>` // key: undefined
`<div>3</div>` // key: undefined
`<div>5</div>` // key: undefined
`<div>2</div>` // key: undefined


// 如果我們給這個(gè)數(shù)組每一項(xiàng)都設(shè)置了唯一的key
[{id: 'A', value: 1}, {id: 'B', value: 2}, {id: 'C', value: 3}, {id: 'D', value: 4}, {id: 'E', value: 5}]

// 它的渲染結(jié)果應(yīng)該是這樣的
`<div>1</div>` // key: A
`<div>2</div>` // key: B
`<div>3</div>` // key: C
`<div>4</div>` // key: D
`<div>5</div>` // key: E

// 將它打亂
[{id: 'D', value: 4}, {id: 'A', value: 1}, {id: 'C', value: 3}, {id: 'E', value: 5}, {id: 'B', value: 2}]

// 渲染結(jié)果是這樣的 期間只發(fā)生了DOM節(jié)點(diǎn)的移動(dòng)
`<div>4</div>` // key: D
`<div>1</div>` // key: A
`<div>3</div>` // key: C
`<div>5</div>` // key: E
`<div>2</div>` // key: B

我們給數(shù)組設(shè)置了key之后數(shù)組的diff效率真的變高了嗎?

并沒(méi)有,因?yàn)樵诤?jiǎn)單模板的數(shù)組渲染中,新舊節(jié)點(diǎn)的key都為undefined,根據(jù)sameVnode的判斷條件,這些新舊節(jié)點(diǎn)的key、tag等屬性全部相同,所以在sameVnode(oldStartVnode, newStartVnode)這一步的時(shí)候就已經(jīng)判定為對(duì)應(yīng)的節(jié)點(diǎn)(不再執(zhí)行頭尾交叉對(duì)比),然后直接進(jìn)行patchVnode,根本沒(méi)有走后面的那些else。每一次循環(huán)新舊節(jié)點(diǎn)都是相對(duì)應(yīng)的,只需要更新其內(nèi)的文本內(nèi)容就可以完成DOM更新,這種原地復(fù)用的效率無(wú)疑是最高的。

而當(dāng)我們?cè)O(shè)置了key之后,則會(huì)根據(jù)頭尾交叉對(duì)比結(jié)果去執(zhí)行下面的if else,進(jìn)行判斷之后還需要執(zhí)行insertBefore等方法移動(dòng)真實(shí)DOM的節(jié)點(diǎn)的位置或者進(jìn)行DOM節(jié)點(diǎn)的添加和刪除,這樣的查找復(fù)用開銷肯定要比不帶key直接原地復(fù)用的開銷要高。

Vue文檔中對(duì)此也進(jìn)行了說(shuō)明:

當(dāng) Vue.js 用 v-for 正在更新已渲染過(guò)的元素列表時(shí),它默認(rèn)用“就地復(fù)用”策略。如果數(shù)據(jù)項(xiàng)的順序被改變,Vue 將不會(huì)移動(dòng) DOM 元素來(lái)匹配數(shù)據(jù)項(xiàng)的順序, 而是簡(jiǎn)單復(fù)用此處每個(gè)元素,并且確保它在特定索引下顯示已被渲染過(guò)的每個(gè)元素。

這個(gè)默認(rèn)的模式是高效的,但是只適用于不依賴子組件狀態(tài)或臨時(shí) DOM 狀態(tài) (例如:表單輸入值) 的列表渲染輸出。

建議盡可能在使用 v-for 時(shí)提供 key,除非遍歷輸出的 DOM 內(nèi)容非常簡(jiǎn)單,或者是刻意依賴默認(rèn)行為以獲取性能上的提升。

所以,簡(jiǎn)單列表的渲染可以不使用key或者用數(shù)組的index作為key(效果等同于不帶key),這種模式下性能最高,但是并不能準(zhǔn)確的更新列表項(xiàng)的狀態(tài)。一旦你需要保存列表項(xiàng)的狀態(tài),那么就需要用使用唯一的key用來(lái)準(zhǔn)確的定位每一個(gè)列表項(xiàng)以及復(fù)用其自身的狀態(tài),而大部分情況下列表組件都有自己的狀態(tài)。

總結(jié)

key在列表渲染中的作用是:在復(fù)雜的列表渲染中快速準(zhǔn)確的找到與newVnode相對(duì)應(yīng)的oldVnode,提升diff效率

以上所述是小編給大家介紹的key在Vue列表渲染時(shí)究竟起到了什么作用詳解整合,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!

相關(guān)文章

  • Vue封裝一個(gè)TodoList的案例與瀏覽器本地緩存的應(yīng)用實(shí)現(xiàn)

    Vue封裝一個(gè)TodoList的案例與瀏覽器本地緩存的應(yīng)用實(shí)現(xiàn)

    這篇文章主要介紹了Vue封裝一個(gè)TodoList的案例與瀏覽器本地緩存的應(yīng)用實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • Vue 嵌套路由使用總結(jié)(推薦)

    Vue 嵌套路由使用總結(jié)(推薦)

    這篇文章主要介紹了Vue 嵌套路由使用總結(jié),本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-01-01
  • Ant Design Vue table組件如何自定義分頁(yè)器

    Ant Design Vue table組件如何自定義分頁(yè)器

    這篇文章主要介紹了Ant Design Vue table組件如何自定義分頁(yè)器問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • vue的一個(gè)分頁(yè)組件的示例代碼

    vue的一個(gè)分頁(yè)組件的示例代碼

    本篇文章主要介紹了vue的一個(gè)分頁(yè)組件的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-12-12
  • 小白教程|一小時(shí)上手最流行的前端框架vue(推薦)

    小白教程|一小時(shí)上手最流行的前端框架vue(推薦)

    這篇文章主要介紹了前端框架vue,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • this.$toast() 了解一下?

    this.$toast() 了解一下?

    這篇文章主要介紹了vue this.$toast()用法 ,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • vue基于Element構(gòu)建自定義樹的示例代碼

    vue基于Element構(gòu)建自定義樹的示例代碼

    本篇文章主要介紹了vue基于Element構(gòu)建自定義樹的示例代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-09-09
  • 如何在寶塔面板部署vue項(xiàng)目

    如何在寶塔面板部署vue項(xiàng)目

    這篇文章主要給大家介紹了關(guān)于如何在寶塔面板部署vue項(xiàng)目的相關(guān)資料,寶塔面板可以通過(guò)Nginx來(lái)部署Vue項(xiàng)目,并解決跨域問(wèn)題,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2023-11-11
  • vue的無(wú)縫滾動(dòng)組件vue-seamless-scroll實(shí)例

    vue的無(wú)縫滾動(dòng)組件vue-seamless-scroll實(shí)例

    本篇文章主要給大家講解了vue的無(wú)縫滾動(dòng)組件vue-seamless-scroll的用法,需要的朋友參考學(xué)習(xí)下吧。
    2017-12-12
  • vue項(xiàng)目如何配置public靜態(tài)資源路徑訪問(wèn)

    vue項(xiàng)目如何配置public靜態(tài)資源路徑訪問(wèn)

    這篇文章主要介紹了vue項(xiàng)目如何配置public靜態(tài)資源路徑訪問(wèn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-11-11

最新評(píng)論