久久精品五月,日韩不卡视频在线观看,国产精品videossex久久发布 ,久久av综合

站長(zhǎng)資訊網(wǎng)
最全最豐富的資訊網(wǎng)站

深入理解vue2中的VNode和diff算法

深入理解vue2中的VNode和diff算法

node.js極速入門(mén)課程:進(jìn)入學(xué)習(xí)

虛擬domdiff算法是vue學(xué)習(xí)過(guò)程中的一個(gè)難點(diǎn),也是面試中必須掌握的一個(gè)知識(shí)點(diǎn)。這兩者相輔相成,是vue框架的核心。今天我們?cè)賮?lái)總結(jié)下vue2中的虛擬domdiff算法。(學(xué)習(xí)視頻分享:vue視頻教程)

什么是 VNode

我們知道,render function 會(huì)被轉(zhuǎn)化成 VNode。VNode 其實(shí)就是一棵以 JavaScript 對(duì)象作為基礎(chǔ)的樹(shù),用對(duì)象屬性來(lái)描述節(jié)點(diǎn),實(shí)際上它只是一層對(duì)真實(shí) DOM 的抽象。最終可以通過(guò)一系列操作使這棵樹(shù)映射到真實(shí)環(huán)境上。

比如有如下template

<template>   <span class="demo" v-show="isShow"> This is a span. </span>  </template>
登錄后復(fù)制

它換成 VNode 以后大概就是下面這個(gè)樣子

{   tag: "span",   data: {     /* 指令集合數(shù)組 */     directives: [       {         /* v-show指令 */         rawName: "v-show",         expression: "isShow",         name: "show",         value: true,       },     ],     /* 靜態(tài)class */     staticClass: "demo",   },   text: undefined,   children: [     /* 子節(jié)點(diǎn)是一個(gè)文本VNode節(jié)點(diǎn) */     {       tag: undefined,       data: undefined,       text: "This is a span.",       children: undefined,     },   ], };
登錄后復(fù)制

總的來(lái)說(shuō),VNode 就是一個(gè) JavaScript 對(duì)象。這個(gè)JavaScript 對(duì)象能完整地表示出真實(shí)DOM

為什么vue要使用 VNode

筆者認(rèn)為有兩點(diǎn)原因

  • 由于 Virtual DOM 是以 JavaScript 對(duì)象為基礎(chǔ)而不依賴(lài)真實(shí)平臺(tái)環(huán)境,所以使它具有了跨平臺(tái)的能力,比如說(shuō)瀏覽器平臺(tái)、Weex、Node 等。

  • 減少操作DOM,任何頁(yè)面的變化,都只使用VNode進(jìn)行操作對(duì)比,只需要在最后一次進(jìn)行掛載更新DOM,避免了頻繁操作DOM,減少了瀏覽器的回流和重繪從而提高頁(yè)面性能。

diff算法

下面我們來(lái)看看組件更新所涉及到的diff算法

前面我們講依賴(lài)收集的時(shí)候有說(shuō)到,渲染watcher傳遞給Watcherget方法其實(shí)是updateComponent方法。

updateComponent = () => {   vm._update(vm._render(), hydrating) }  new Watcher(vm, updateComponent, noop, {   before () {     if (vm._isMounted) {       callHook(vm, 'beforeUpdate')     }   } }, true /* isRenderWatcher */)
登錄后復(fù)制

所以組件在響應(yīng)式數(shù)據(jù)發(fā)生變化的時(shí)候會(huì)再次觸發(fā)該方法,接下來(lái)我們來(lái)詳細(xì)分析一下updateComponent里面的_update方法。

_update

_update方法中做了初始渲染和更新的區(qū)分,雖然都是調(diào)用__patch__方法,但是傳遞的參數(shù)不一樣。

// src/core/instance/lifecycle.js  Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {   const vm: Component = this   const prevEl = vm.$el   const prevVnode = vm._vnode   vm._vnode = vnode   // 初次渲染沒(méi)有 prevVnode,組件更新才會(huì)有   if (!prevVnode) {     // 初次渲染     vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)   } else {     // 更新     vm.$el = vm.__patch__(prevVnode, vnode)   }      // ... }
登錄后復(fù)制

下面我們?cè)賮?lái)看看__patch__方法

__patch__

patch方法接收四個(gè)參數(shù),由于初始渲染的時(shí)候oldVnodevm.$elnull,所以初始渲染是沒(méi)有oldVnode

// src/core/vdom/patch.js  return function patch (oldVnode, vnode, hydrating, removeOnly) {   // 新節(jié)點(diǎn)不存在,只有oldVnode就直接銷(xiāo)毀,然后返回   if (isUndef(vnode)) {     if (isDef(oldVnode)) invokeDestroyHook(oldVnode)     return   }    let isInitialPatch = false   const insertedVnodeQueue = []   // 沒(méi)有老節(jié)點(diǎn),直接創(chuàng)建,也就是初始渲染   if (isUndef(oldVnode)) {     isInitialPatch = true     createElm(vnode, insertedVnodeQueue)   } else {     const isRealElement = isDef(oldVnode.nodeType)     // 不是真實(shí)dom,并且是相同節(jié)點(diǎn)走patch     if (!isRealElement && sameVnode(oldVnode, vnode)) {       // 這里才會(huì)涉及到diff算法       patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)     } else {       if (isRealElement) {         // ...       }        // replacing existing element       const oldElm = oldVnode.elm       const parentElm = nodeOps.parentNode(oldElm)        // 1.創(chuàng)建一個(gè)新節(jié)點(diǎn)       createElm(         vnode,         insertedVnodeQueue,         // extremely rare edge case: do not insert if old element is in a         // leaving transition. Only happens when combining transition +         // keep-alive + HOCs. (#4590)         oldElm._leaveCb ? null : parentElm,         nodeOps.nextSibling(oldElm)       )        // 2.更新父節(jié)點(diǎn)占位符       if (isDef(vnode.parent)) {         let ancestor = vnode.parent         const patchable = isPatchable(vnode)         while (ancestor) {           for (let i = 0; i < cbs.destroy.length; ++i) {             cbs.destroy[i](ancestor)           }           ancestor.elm = vnode.elm           if (patchable) {             for (let i = 0; i < cbs.create.length; ++i) {               cbs.create[i](emptyNode, ancestor)             }                          const insert = ancestor.data.hook.insert             if (insert.merged) {               // start at index 1 to avoid re-invoking component mounted hook               for (let i = 1; i < insert.fns.length; i++) {                 insert.fns[i]()               }             }           } else {             registerRef(ancestor)           }           ancestor = ancestor.parent         }       }        // 3.刪除老節(jié)點(diǎn)       if (isDef(parentElm)) {         removeVnodes([oldVnode], 0, 0)       } else if (isDef(oldVnode.tag)) {         invokeDestroyHook(oldVnode)       }     }   }        //觸發(fā)插入鉤子   invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)   return vnode.elm }
登錄后復(fù)制

patch方法大概流程如下:

  • 沒(méi)有新節(jié)點(diǎn)只有老節(jié)點(diǎn)直接刪除老節(jié)點(diǎn)。

  • 只有新節(jié)點(diǎn)沒(méi)有老節(jié)點(diǎn)直接添加新節(jié)點(diǎn)。

  • 既有新節(jié)點(diǎn)又有老節(jié)點(diǎn)則判斷是不是相同節(jié)點(diǎn),相同則進(jìn)入pathVnode。patchVnode我們后面會(huì)重點(diǎn)分析。

  • 既有新節(jié)點(diǎn)又有老節(jié)點(diǎn)則判斷是不是相同節(jié)點(diǎn),不相同則直接刪除老節(jié)點(diǎn)添加新節(jié)點(diǎn)。

我們?cè)賮?lái)看看它是怎么判斷是同一個(gè)節(jié)點(diǎn)的。

// src/core/vdom/patch.js  function sameVnode (a, b) {   return (     a.key === b.key &&     a.asyncFactory === b.asyncFactory && (       (         a.tag === b.tag &&         a.isComment === b.isComment &&         isDef(a.data) === isDef(b.data) &&         sameInputType(a, b)       ) || (         isTrue(a.isAsyncPlaceholder) &&         isUndef(b.asyncFactory.error)       )     )   ) }  function sameInputType (a, b) {   if (a.tag !== 'input') return true   let i   const typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type   const typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type   return typeA === typeB || isTextInputType(typeA) && isTextInputType(typeB) }
登錄后復(fù)制

判斷兩個(gè)VNode節(jié)點(diǎn)是否是同一個(gè)節(jié)點(diǎn),需要同時(shí)滿足以下條件

  • key相同

  • 都有異步組件工廠函數(shù)

  • tag(當(dāng)前節(jié)點(diǎn)的標(biāo)簽名)相同

  • isComment是否同為注釋節(jié)點(diǎn)

  • 是否data(當(dāng)前節(jié)點(diǎn)對(duì)應(yīng)的對(duì)象,包含了具體的一些數(shù)據(jù)信息,是一個(gè)VNodeData類(lèi)型)

  • 當(dāng)標(biāo)簽是<input>的時(shí)候,type必須相同

當(dāng)兩個(gè)VNodetag、key、isComment都相同,并且同時(shí)定義或未定義data的時(shí)候,且如果標(biāo)簽為input則type必須相同。這時(shí)候這兩個(gè)VNode則算sameVnode,可以直接進(jìn)行patchVnode操作。

patchVnode

下面我們?cè)賮?lái)詳細(xì)分析下patchVnode方法。

// src/core/vdom/patch.js  function patchVnode (   oldVnode,   vnode,   insertedVnodeQueue,   ownerArray,   index,   removeOnly ) {   // 兩個(gè)vnode相同則直接返回   if (oldVnode === vnode) {     return   }    if (isDef(vnode.elm) && isDef(ownerArray)) {     // clone reused vnode     vnode = ownerArray[index] = cloneVNode(vnode)   }    const elm = vnode.elm = oldVnode.elm    if (isTrue(oldVnode.isAsyncPlaceholder)) {     if (isDef(vnode.asyncFactory.resolved)) {       hydrate(oldVnode.elm, vnode, insertedVnodeQueue)     } else {       vnode.isAsyncPlaceholder = true     }     return   }    /*     如果新舊VNode都是靜態(tài)的,同時(shí)它們的key相同(代表同一節(jié)點(diǎn)),     并且新的VNode是clone或者是標(biāo)記了once(標(biāo)記v-once屬性,只渲染一次),     那么只需要替換componentInstance即可。   */   if (isTrue(vnode.isStatic) &&     isTrue(oldVnode.isStatic) &&     vnode.key === oldVnode.key &&     (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))   ) {     vnode.componentInstance = oldVnode.componentInstance     return   }    let i   const data = vnode.data   /*調(diào)用prepatch鉤子*/   if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {     i(oldVnode, vnode)   }    // 獲取新老虛擬節(jié)點(diǎn)的子節(jié)點(diǎn)   const oldCh = oldVnode.children   const ch = vnode.children   if (isDef(data) && isPatchable(vnode)) {     for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)     if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)   }      // 新節(jié)點(diǎn)不是文本節(jié)點(diǎn)   if (isUndef(vnode.text)) {     /*新老節(jié)點(diǎn)均有children子節(jié)點(diǎn),則對(duì)子節(jié)點(diǎn)進(jìn)行diff操作,調(diào)用updateChildren*/     if (isDef(oldCh) && isDef(ch)) {       if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)     /*如果只有新節(jié)點(diǎn)有子節(jié)點(diǎn),先清空elm文本內(nèi)容,然后為當(dāng)前DOM節(jié)點(diǎn)加入子節(jié)點(diǎn)。*/     } else if (isDef(ch)) {       if (process.env.NODE_ENV !== 'production') {         checkDuplicateKeys(ch)       }       if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')       addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)     /*如果只有老節(jié)點(diǎn)有子節(jié)點(diǎn),則移除elm所有子節(jié)點(diǎn)*/     } else if (isDef(oldCh)) {       removeVnodes(oldCh, 0, oldCh.length - 1)     /*當(dāng)新老節(jié)點(diǎn)都無(wú)子節(jié)點(diǎn)的時(shí)候,因?yàn)檫@個(gè)邏輯中新節(jié)點(diǎn)text不存在,所以直接去除ele的文本*/     } else if (isDef(oldVnode.text)) {       nodeOps.setTextContent(elm, '')     }   // 新節(jié)點(diǎn)是文本節(jié)點(diǎn),如果文本不一樣就設(shè)置新的文本     } else if (oldVnode.text !== vnode.text) {     nodeOps.setTextContent(elm, vnode.text)   }   /*調(diào)用postpatch鉤子*/   if (isDef(data)) {     if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)   } }
登錄后復(fù)制

patchVnode方法大概流程如下:

1.新老節(jié)點(diǎn)相同就直接返回。

2.如果新舊VNode都是靜態(tài)的,同時(shí)它們的key相同(代表同一節(jié)點(diǎn)),并且新的VNode是clone或者是標(biāo)記了once(標(biāo)記v-once屬性,只渲染一次),那么只需要替換componentInstance即可。

3.新節(jié)點(diǎn)不是文本節(jié)點(diǎn),新老節(jié)點(diǎn)均有children子節(jié)點(diǎn),則對(duì)子節(jié)點(diǎn)進(jìn)行diff操作,調(diào)用updateChildren,這個(gè)updateChildrendiff算法的核心,后面我們會(huì)重點(diǎn)說(shuō)。

4.新節(jié)點(diǎn)不是文本節(jié)點(diǎn),如果老節(jié)點(diǎn)沒(méi)有子節(jié)點(diǎn)而新節(jié)點(diǎn)存在子節(jié)點(diǎn),先清空老節(jié)點(diǎn)DOM的文本內(nèi)容,然后為當(dāng)前DOM節(jié)點(diǎn)加入子節(jié)點(diǎn)。

5.新節(jié)點(diǎn)不是文本節(jié)點(diǎn),當(dāng)新節(jié)點(diǎn)沒(méi)有子節(jié)點(diǎn)而老節(jié)點(diǎn)有子節(jié)點(diǎn)的時(shí)候,則移除該DOM節(jié)點(diǎn)的所有子節(jié)點(diǎn)。

6.新節(jié)點(diǎn)不是文本節(jié)點(diǎn),并且新老節(jié)點(diǎn)都無(wú)子節(jié)點(diǎn)的時(shí)候,只需要將老節(jié)點(diǎn)文本清空。

7.新節(jié)點(diǎn)是文本節(jié)點(diǎn),并且新老節(jié)點(diǎn)文本不一樣,則進(jìn)行文本的替換。

updateChildren(diff算法核心)

updateChildrendiff算法的核心,下面我們來(lái)重點(diǎn)分析。

深入理解vue2中的VNode和diff算法

這兩張圖代表舊的VNode與新VNode進(jìn)行patch的過(guò)程,他們只是在同層級(jí)的VNode之間進(jìn)行比較得到變化(相同顏色的方塊代表互相進(jìn)行比較的VNode節(jié)點(diǎn)),然后修改變化的視圖,所以十分高效。所以Diff算法是:深度優(yōu)先算法。 時(shí)間復(fù)雜度:O(n)

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    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]     // 老 VNode 節(jié)點(diǎn)的頭部與新 VNode 節(jié)點(diǎn)的頭部是相同的 VNode 節(jié)點(diǎn),直接進(jìn)行 patchVnode,同時(shí) oldStartIdx 與 newStartIdx 向后移動(dòng)一位。     } else if (sameVnode(oldStartVnode, newStartVnode)) {       patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)       oldStartVnode = oldCh[++oldStartIdx]       newStartVnode = newCh[++newStartIdx]     // 兩個(gè) VNode 的結(jié)尾是相同的 VNode,同樣進(jìn)行 patchVnode 操作。并將 oldEndVnode 與 newEndVnode 向前移動(dòng)一位。     } else if (sameVnode(oldEndVnode, newEndVnode)) {       patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)       oldEndVnode = oldCh[--oldEndIdx]       newEndVnode = newCh[--newEndIdx]     // oldStartVnode 與 newEndVnode 符合 sameVnode 的時(shí)候,     // 將 oldStartVnode.elm 這個(gè)節(jié)點(diǎn)直接移動(dòng)到 oldEndVnode.elm 這個(gè)節(jié)點(diǎn)的后面即可。     // 然后 oldStartIdx 向后移動(dòng)一位,newEndIdx 向前移動(dòng)一位。     } 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]     // oldEndVnode 與 newStartVnode 符合 sameVnode 時(shí),     // 將 oldEndVnode.elm 插入到 oldStartVnode.elm 前面。     // oldEndIdx 向前移動(dòng)一位,newStartIdx 向后移動(dòng)一位。     } 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 {       // createKeyToOldIdx 的作用是產(chǎn)生 key 與 index 索引對(duì)應(yīng)的一個(gè) map 表       if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)       idxInOld = isDef(newStartVnode.key)         ? oldKeyToIdx[newStartVnode.key]         : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)       // 如果沒(méi)有找到相同的節(jié)點(diǎn),則通過(guò) createElm 創(chuàng)建一個(gè)新節(jié)點(diǎn),并將 newStartIdx 向后移動(dòng)一位。       if (isUndef(idxInOld)) { // New element         createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)       } else {         vnodeToMove = oldCh[idxInOld]         // 如果找到了節(jié)點(diǎn),同時(shí)它符合 sameVnode,則將這兩個(gè)節(jié)點(diǎn)進(jìn)行 patchVnode,將該位置的老節(jié)點(diǎn)賦值 undefined         // 同時(shí)將 newStartVnode.elm 插入到 oldStartVnode.elm 的前面         if (sameVnode(vnodeToMove, newStartVnode)) {           patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)           oldCh[idxInOld] = undefined           canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)         } else {           // 如果不符合 sameVnode,只能創(chuàng)建一個(gè)新節(jié)點(diǎn)插入到 parentElm 的子節(jié)點(diǎn)中,newStartIdx 往后移動(dòng)一位。           createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)         }       }       newStartVnode = newCh[++newStartIdx]     }   }   // 當(dāng) while 循環(huán)結(jié)束以后,如果 oldStartIdx > oldEndIdx,說(shuō)明老節(jié)點(diǎn)比對(duì)完了,但是新節(jié)點(diǎn)還有多的,   // 需要將新節(jié)點(diǎn)插入到真實(shí) DOM 中去,調(diào)用 addVnodes 將這些節(jié)點(diǎn)插入即可。   if (oldStartIdx > oldEndIdx) {     refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm     addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)   // 如果滿足 newStartIdx > newEndIdx 條件,說(shuō)明新節(jié)點(diǎn)比對(duì)完了,老節(jié)點(diǎn)還有多,   // 將這些無(wú)用的老節(jié)點(diǎn)通過(guò) removeVnodes 批量刪除即可。   } else if (newStartIdx > newEndIdx) {     removeVnodes(oldCh, oldStartIdx, oldEndIdx)   } }
登錄后復(fù)制

vue2diff算法采用的是雙端比較,所謂雙端比較就是新列表舊列表兩個(gè)列表的頭與尾互相對(duì)比,在對(duì)比的過(guò)程中指針會(huì)逐漸向內(nèi)靠攏,直到某一個(gè)列表的節(jié)點(diǎn)全部遍歷過(guò),對(duì)比停止。

首尾對(duì)比的四種情況

我們首先來(lái)看看首尾對(duì)比的四種情況。

  • 使用舊列表的頭一個(gè)節(jié)點(diǎn)oldStartNode新列表的頭一個(gè)節(jié)點(diǎn)newStartNode對(duì)比

  • 使用舊列表的最后一個(gè)節(jié)點(diǎn)oldEndNode新列表的最后一個(gè)節(jié)點(diǎn)newEndNode對(duì)比

  • 使用舊列表的頭一個(gè)節(jié)點(diǎn)oldStartNode新列表的最后一個(gè)節(jié)點(diǎn)newEndNode對(duì)比

  • 使用舊列表的最后一個(gè)節(jié)點(diǎn)oldEndNode新列表的頭一個(gè)節(jié)點(diǎn)newStartNode對(duì)比

首先是 oldStartVnodenewStartVnode 符合 sameVnode 時(shí),說(shuō)明老 VNode 節(jié)點(diǎn)的頭部與新 VNode 節(jié)點(diǎn)的頭部是相同的 VNode 節(jié)點(diǎn),直接進(jìn)行 patchVnode,同時(shí) oldStartIdxnewStartIdx 向后移動(dòng)一位。

深入理解vue2中的VNode和diff算法

其次是 oldEndVnodenewEndVnode 符合 sameVnode,也就是兩個(gè) VNode 的結(jié)尾是相同的 VNode,同樣進(jìn)行 patchVnode 操作并將 oldEndVnodenewEndVnode 向前移動(dòng)一位。

深入理解vue2中的VNode和diff算法

接下來(lái)是兩種交叉的情況。

先是 oldStartVnodenewEndVnode 符合 sameVnode 的時(shí)候,也就是老 VNode 節(jié)點(diǎn)的頭部與新 VNode 節(jié)點(diǎn)的尾部是同一節(jié)點(diǎn)的時(shí)候,將 oldStartVnode.elm 這個(gè)節(jié)點(diǎn)直接移動(dòng)到 oldEndVnode.elm 這個(gè)節(jié)點(diǎn)的后面即可。然后 oldStartIdx 向后移動(dòng)一位,newEndIdx 向前移動(dòng)一位。

深入理解vue2中的VNode和diff算法

同理,oldEndVnodenewStartVnode 符合 sameVnode 時(shí),也就是老 VNode 節(jié)點(diǎn)的尾部與新 VNode 節(jié)點(diǎn)的頭部是同一節(jié)點(diǎn)的時(shí)候,將 oldEndVnode.elm 插入到 oldStartVnode.elm 前面。同樣的,oldEndIdx 向前移動(dòng)一位,newStartIdx 向后移動(dòng)一位。

深入理解vue2中的VNode和diff算法

最后是當(dāng)以上情況都不符合的時(shí)候,這種情況怎么處理呢?

查找對(duì)比

那就是查找對(duì)比。

首先通過(guò)createKeyToOldIdx方法生成oldVnodekeyindex 索引對(duì)應(yīng)的一個(gè) map 表。

然后我們根據(jù)newStartVnode.key,可以快速地從 oldKeyToIdxcreateKeyToOldIdx 的返回值)中獲取相同 key 的節(jié)點(diǎn)的索引 idxInOld,然后找到相同的節(jié)點(diǎn)。

這里又分三種情況

  • 如果沒(méi)有找到相同的節(jié)點(diǎn),則通過(guò) createElm 創(chuàng)建一個(gè)新節(jié)點(diǎn),并將 newStartIdx 向后移動(dòng)一位。

  • 如果找到了節(jié)點(diǎn),同時(shí)它符合 sameVnode,則將這兩個(gè)節(jié)點(diǎn)進(jìn)行 patchVnode,將該位置的老節(jié)點(diǎn)賦值 undefined(之后如果還有新節(jié)點(diǎn)與該節(jié)點(diǎn)key相同可以檢測(cè)出來(lái)提示已有重復(fù)的 key ),同時(shí)將 newStartVnode.elm 插入到 oldStartVnode.elm 的前面。同理,newStartIdx 往后移動(dòng)一位。

深入理解vue2中的VNode和diff算法

  • 如果不符合 sameVnode,只能創(chuàng)建一個(gè)新節(jié)點(diǎn)插入到 parentElm 的子節(jié)點(diǎn)中,newStartIdx 往后移動(dòng)一位。

深入理解vue2中的VNode和diff算法

添加、刪除節(jié)點(diǎn)

最后一步就很容易啦,當(dāng) while 循環(huán)結(jié)束以后,如果 oldStartIdx > oldEndIdx,說(shuō)明老節(jié)點(diǎn)比對(duì)完了,但是新節(jié)點(diǎn)還有多的,需要將新節(jié)點(diǎn)插入到真實(shí) DOM 中去,調(diào)用 addVnodes 將這些節(jié)點(diǎn)插入即可。

深入理解vue2中的VNode和diff算法

同理,如果滿足 newStartIdx > newEndIdx 條件,說(shuō)明新節(jié)點(diǎn)比對(duì)完了,老節(jié)點(diǎn)還有多,將這些無(wú)用的老節(jié)點(diǎn)通過(guò) removeVnodes 批量刪除即可。

深入理解vue2中的VNode和diff算法

總結(jié)

Diff算法是一種對(duì)比算法。對(duì)比兩者是舊虛擬DOM和新虛擬DOM,對(duì)比出是哪個(gè)虛擬節(jié)點(diǎn)更改了,找出這個(gè)虛擬節(jié)點(diǎn),并只更新這個(gè)虛擬節(jié)點(diǎn)所對(duì)應(yīng)的真實(shí)節(jié)點(diǎn),而不用更新其他數(shù)據(jù)沒(méi)發(fā)生改變的節(jié)點(diǎn),實(shí)現(xiàn)精準(zhǔn)地更新真實(shí)DOM,進(jìn)而提高效率和性能。

精準(zhǔn)主要體現(xiàn)在,diff 算法首先就是找到可復(fù)用的節(jié)點(diǎn),然后移動(dòng)到正確的位置。當(dāng)元素沒(méi)有找到的話再來(lái)創(chuàng)建新節(jié)點(diǎn)。

擴(kuò)展

vue中為什么需要使用key,它的作用是什么?

keyVuevnode 的唯一標(biāo)記,通過(guò)這個(gè) keydiff 操作可以更準(zhǔn)確、更快速。

  1. 更準(zhǔn)確:因?yàn)閹?key 就不是就地復(fù)用了,在 sameNode 函數(shù) a.key === b.key 對(duì)比中可以避免就地復(fù)用的情況。所以會(huì)更加準(zhǔn)確。
  2. 更快速:利用 key 的唯一性生成 map 對(duì)象來(lái)獲取對(duì)應(yīng)節(jié)點(diǎn),比遍歷方式更快。

為什么不推薦使用index作為key

當(dāng)我們的列表只涉及到 展示,不涉及到排序、刪除、添加的時(shí)候使用index作為key是沒(méi)什么問(wèn)題的。因?yàn)榇藭r(shí)的index在每個(gè)元素上是唯一的。

但是如果涉及到排序、刪除、添加的時(shí)候就不能再使用index作為key了,因?yàn)槊總€(gè)元素key不再唯一了。不唯一的key,對(duì)diff算法沒(méi)有任何幫助,寫(xiě)和沒(méi)寫(xiě)是一樣的。

(學(xué)習(xí)視頻分享:web前端開(kāi)發(fā)、編程基礎(chǔ)視頻)

贊(0)
分享到: 更多 (0)
?
網(wǎng)站地圖   滬ICP備18035694號(hào)-2    滬公網(wǎng)安備31011702889846號(hào)
久久精品五月,日韩不卡视频在线观看,国产精品videossex久久发布 ,久久av综合
久久青青视频| 国产一区视频在线观看免费| 欧美日韩一区二区国产 | 欧美成人精品| 亚州av一区| 久久久久黄色| 午夜日本精品| 欧美韩一区二区| 欧美日韩精品免费观看视欧美高清免费大片 | 日本精品影院| 国产日韩综合| 欧美激情亚洲| 免费中文字幕日韩欧美| 国产另类在线| 欧美不卡在线| 国产精久久久| 免费成人在线影院| 黄毛片在线观看| 亚洲精品护士| 欧美日韩色图| 国产精品免费不| 性色av一区二区怡红| 久久久久久色| 久久国产成人午夜av影院宅| 日韩精品成人在线观看| 欧美日中文字幕| 国产精品igao视频网网址不卡日韩| 欧美亚洲精品在线| 久久久久亚洲精品中文字幕| 免费在线视频一区| 欧美午夜精品一区二区三区电影| 麻豆国产精品| 亚洲精品视频一二三区| 欧美91视频| 在线亚洲人成| 精品中文在线| 国产剧情一区二区在线观看| 四虎成人精品一区二区免费网站| 久久久成人网| 国产精品久久观看| 国产精品.xx视频.xxtv| 亚洲人成网站在线在线观看| 国产一区亚洲| 日韩精品dvd| 久久a爱视频| 欧美色综合网| 午夜在线精品偷拍| bbw在线视频| 欧美91在线| 日韩不卡一区二区| 欧美综合国产| 黄页网站一区| 激情综合在线| 日韩中文影院| 国产一区二区三区不卡av| 日本强好片久久久久久aaa| 一区视频在线| 久久久久蜜桃| 成人精品视频| 免费看一区二区三区| 国产乱人伦丫前精品视频| 视频国产精品| 亚洲女人av| 黑丝一区二区三区| 日韩高清成人| 免费看av不卡| 天堂√中文最新版在线| 国产不卡精品在线| 欧美精品99| 国产精品va视频| 久久不卡国产精品一区二区| 国产伦一区二区三区| 日韩国产欧美三级| 四虎在线精品| 婷婷久久免费视频| 亚洲ww精品| 日韩高清国产一区在线| 午夜电影一区| 日本电影久久久| 欧美亚洲专区| 欧美日韩伊人| 久久精品99国产国产精| 日韩精品久久久久久久软件91| 亚洲精品麻豆| 亚洲精品一级| 久久狠狠久久| 国产精品免费大片| 国产高清日韩| 粉嫩av一区二区三区四区五区 | 日韩av首页| 久久精品免费一区二区三区| 亚洲成人二区| 欧美精品资源| 亚洲作爱视频| 亚洲资源在线| 日韩欧美激情电影| 国产三级一区| 精品在线网站观看| 久久蜜桃精品| 欧美日韩高清| 中文亚洲欧美| 日韩在线一二三区| 日韩av中文在线观看| 国产精品一区二区精品视频观看| 免费在线亚洲| 秋霞国产精品| 久久国产精品久久久久久电车 | 久久精品 人人爱| 麻豆精品视频在线观看| 欧洲av不卡| 亚洲激情黄色| 日韩va欧美va亚洲va久久| 久久三级中文| 91精品啪在线观看国产18| 91精品成人| 亚洲精品动态| 美女毛片一区二区三区四区最新中文字幕亚洲 | 国产剧情一区二区在线观看| 精品午夜视频| 欧美日韩水蜜桃| 婷婷综合成人| 中文在线а√天堂| 国产精品美女久久久浪潮软件| 亚洲最新av| 精品视频国产| 亚洲国产成人精品女人| 日韩中文一区二区| 精品一区二区三区的国产在线观看| 999久久久国产精品| 亚洲毛片视频| 成人片免费看| 亚洲精品一级| 美女av在线免费看| 亚洲综合精品| 精品久久91| 亚洲欧美日韩视频二区| 奇米狠狠一区二区三区| 日本а中文在线天堂| 国产精品日本欧美一区二区三区| 日本va欧美va欧美va精品| xxxxx性欧美特大| 日韩欧美久久| 久久免费高清| 国产精品亲子伦av一区二区三区| 欧美日韩精品一区二区视频| 久久国产精品免费一区二区三区| 亚洲伊人av| 亚洲欧美在线专区| 日韩成人高清| 国产调教一区二区三区| 特黄特色欧美大片| 国产精品日本一区二区三区在线| 欧美成人综合| 精品网站999| 亚洲色图国产| 日韩精品一区二区三区免费观看| 欧美日韩国产一区二区在线观看| 少妇久久久久| 欧美91在线| 婷婷精品在线| 欧美.日韩.国产.一区.二区 | 午夜av不卡| 欧美色综合网| 国产亚洲精品久久久久婷婷瑜伽| 精品一区二区三区中文字幕在线| 一区二区精品| 四虎4545www国产精品 | 欧美日韩黄网站| 国产亚洲激情| 中文另类视频| 麻豆一区二区三| 婷婷五月色综合香五月| 欧美+亚洲+精品+三区| 精品淫伦v久久水蜜桃| 日韩精彩视频在线观看| 国产伊人精品| 日韩大片免费观看| 久久中文欧美| 久久精品99国产国产精| 久久亚洲欧洲| 欧美午夜精品一区二区三区电影| 精品网站aaa| 国产精品二区影院| 日韩精品电影一区亚洲| 免费观看在线综合色| 欧美日韩国产综合网| av高清不卡| 国产成年精品| 麻豆国产欧美一区二区三区 | 青草国产精品| 亚洲婷婷丁香| 亚洲专区在线| 一区福利视频| 免费黄色成人| 久久久五月天| 久久黄色影院| 99久久www免费| 日本不良网站在线观看| 国产精品二区不卡| 国产不卡一区|