溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Vue2中的雙端diff算法怎么更新節(jié)點(diǎn)

發(fā)布時(shí)間:2022-07-19 09:20:26 來(lái)源:億速云 閱讀:130 作者:iii 欄目:編程語(yǔ)言

這篇文章主要介紹了Vue2中的雙端diff算法怎么更新節(jié)點(diǎn)的相關(guān)知識(shí),內(nèi)容詳細(xì)易懂,操作簡(jiǎn)單快捷,具有一定借鑒價(jià)值,相信大家閱讀完這篇Vue2中的雙端diff算法怎么更新節(jié)點(diǎn)文章都會(huì)有所收獲,下面我們一起來(lái)看看吧。

Vue2中的雙端diff算法怎么更新節(jié)點(diǎn)

預(yù)備知識(shí)

diff 算法作用

聊 diff 算法前得認(rèn)識(shí)一下它是干嘛的。

我們知道在網(wǎng)頁(yè)運(yùn)行中,我們改變一些數(shù)據(jù),它們可能會(huì)影響到 DOM 樹。如何在頁(yè)面中展示最新的數(shù)據(jù)呢,最簡(jiǎn)單的方式就是整棵樹推到重建,當(dāng)然這樣會(huì)導(dǎo)致大量的浪費(fèi),所以 Vue 使用虛擬 DOM 保存頁(yè)面中 DOM 樹的狀態(tài),在數(shù)據(jù)變化后,構(gòu)建一棵新的虛擬 DOM 樹,找到前后兩顆樹的不同之處,針對(duì)性地更新真實(shí) DOM。

而如何找到兩顆樹的不同之處,減少 DOM 元素的銷毀與重建,就是 diff 算法的作用

虛擬 DOM

虛擬 DOM,又稱虛擬節(jié)點(diǎn)(vnode),簡(jiǎn)單來(lái)說(shuō)就是包含 DOM 元素信息的對(duì)象,一般由 h 函數(shù)創(chuàng)建,下面這個(gè)對(duì)象就可以看成是一個(gè)虛擬節(jié)點(diǎn)

const vnode = {
    tag: 'div', // 標(biāo)簽類型
    text: '', // 文本內(nèi)容
    children: undefined, // 子節(jié)點(diǎn)
}

對(duì)于這段 HTML

<div>
    <p>a</p>
    <p>b</p>
    <p>c</p>
</div>

轉(zhuǎn)換成 vnode 是這樣的

const vnode = {
    tag: 'div', // 標(biāo)簽類型
    text: undefined, // 文本內(nèi)容
    children: [ // 子節(jié)點(diǎn)
        {
            tag: 'p',
            text: 'a',
            children: undefined,
        },
        {
            tag: 'p',
            text: 'b',
            children: undefined,
        },
        {
            tag: 'p',
            text: 'c',
            children: undefined,
        },
    ],
}

因?yàn)槲覀冃枰ㄟ^(guò)虛擬節(jié)點(diǎn)去操作真實(shí) DOM,所以 vnode 身上有個(gè) elm 屬性指向真實(shí)的 DOM 元素。而且在之后的 diff 算法中,還會(huì)用到一個(gè) key 來(lái)對(duì)節(jié)點(diǎn)進(jìn)行唯一標(biāo)識(shí),所以下文中的 vnode 是這樣的對(duì)象

const vnode = {
    tag: 'div', // 標(biāo)簽類型
    text: '', // 文本內(nèi)容
    children: undefined, // 子節(jié)點(diǎn)
    elm: undefined, // 對(duì)應(yīng)的真實(shí)DOM
    key: '', // 唯一標(biāo)識(shí)
}

Vue 的虛擬節(jié)點(diǎn)還有很多屬性,不過(guò)與 diff 算法無(wú)關(guān),就不列舉了

說(shuō)明一點(diǎn),虛擬節(jié)點(diǎn)的 text 和 children 不會(huì)同時(shí)有值。在有 children 屬性的情況下,text 中的內(nèi)容會(huì)轉(zhuǎn)化為一個(gè)文本節(jié)點(diǎn)置入 children 數(shù)組中

預(yù)備函數(shù)

為了使等會(huì)的代碼實(shí)現(xiàn)更簡(jiǎn)單,我們準(zhǔn)備幾個(gè)函數(shù),功能不難,直接貼代碼了

我們首先需要就是一個(gè)將虛擬節(jié)點(diǎn)轉(zhuǎn)換為真實(shí) DOM 的函數(shù)

// 根據(jù)虛擬節(jié)點(diǎn)創(chuàng)建真實(shí)節(jié)點(diǎn)
function createElement(vnode) {
    const dom = document.createElement(vnode.tag)
    if (vnode.children) {
        // 包含子節(jié)點(diǎn),遞歸創(chuàng)建
        for (let i = 0; i < vnode.children.length; i++) {
            const childDom = createElement(vnode.children[i])
            dom.appendChild(childDom)
        }
    } else {
        // 內(nèi)部是文字
        dom.innerHTML = vnode.text
    }
    // 補(bǔ)充elm屬性
    vnode.elm = dom
    return dom
}

以及三個(gè)工具函數(shù)

// 判斷是否未定義
function isUndef(v) {
    return v === undefined || v === null
}
// 判斷是否已定義
function isDef(v) {
    return v !== undefined && v !== null
}
// 檢查是否可復(fù)用
function checkSameVnode(a, b) {
    return a.tag === b.tag && a.key === b.key
}

patchVnode

當(dāng)數(shù)據(jù)更新后,Vue 創(chuàng)建出一棵新 vnode,然后執(zhí)行 patchVnode 函數(shù)比較新老兩個(gè)虛擬節(jié)點(diǎn)的不同之處,然后根據(jù)情況進(jìn)行處理

function patchVnode(newVnode, oldVnode) {}

首先判斷新舊兩個(gè)虛擬節(jié)點(diǎn)是同一對(duì)象,如果是的話就不用處理

if (oldVnode === newVnode) return

然后將舊節(jié)點(diǎn)的 DOM 元素賦給新節(jié)點(diǎn),并獲取新舊節(jié)點(diǎn)的 children 屬性

let elm = (newVnode.elm = oldVnode.elm)
let oldCh = oldVnode.children
let newCh = newVnode.children

這里可以直接賦值是因?yàn)檎{(diào)用 patchVnode 的新舊節(jié)點(diǎn)它們的 tag 與 key 是一定相同的,在下文會(huì)有講解

然后根據(jù)兩個(gè)節(jié)點(diǎn)內(nèi)容,決定如何更新 DOM

1、新舊兩個(gè)節(jié)點(diǎn)內(nèi)容都是文本。修改文本即可

if (isUndef(oldCh) && isUndef(newCh)) {
    if (newVnode.text !== oldVnode.text) {
        elm.innerText = newVnode.text
    }
}

2、舊節(jié)點(diǎn)有子節(jié)點(diǎn),新節(jié)點(diǎn)內(nèi)容是文本。清空舊節(jié)點(diǎn)內(nèi)容,改為文本

if (isDef(oldCh) && isUndef(newCh)) {
    elm.innerHTML = newVnode.text
}

3、舊節(jié)點(diǎn)內(nèi)容是文本,新節(jié)點(diǎn)有子節(jié)點(diǎn)。清空舊節(jié)點(diǎn)內(nèi)容,遍歷新節(jié)點(diǎn)生成子 DOM 元素插入節(jié)點(diǎn)中

if (isUndef(oldCh) && isDef(newCh)) {
    elm.innerHTML = ''
    for (let i = 0, n = newCh.length; i < n; i++){
        elm.appendChild(createElement(newCh[i]))
    }
}

4、新舊節(jié)點(diǎn)都有子節(jié)點(diǎn)。調(diào)用 updateChildren 來(lái)處理,該函數(shù)在下一章講解

if (isDef(oldCh) && isDef(newCh)) {
    updateChildren(elm, oldCh, newCh)
}

情況 4 可以與情況 3 的處理一樣,清空舊節(jié)點(diǎn),然后遍歷生成新 DOM。但是我們要知道,創(chuàng)建 DOM 元素是一件非常耗時(shí)的工作,而且新舊子節(jié)點(diǎn)在大多時(shí)候都是相同的,如果可以復(fù)用,將極大優(yōu)化我們的性能。

那我們要如何判定一個(gè)節(jié)點(diǎn)是否可以復(fù)用呢?

這就需要 Vue 的使用者來(lái)幫忙了,使用者在節(jié)點(diǎn)上定義 key 屬性,告訴 Vue 哪些節(jié)點(diǎn)可以復(fù)用

只要標(biāo)簽類型與 key 值都相等,就說(shuō)明當(dāng)前元素可以被復(fù)用

然而在我們的項(xiàng)目中,一般只有在 v-for 中才設(shè)置 key,其他節(jié)點(diǎn)都沒(méi)設(shè)置 key

其實(shí)沒(méi)有設(shè)置 key 的節(jié)點(diǎn),它們的 key 值默認(rèn)相等

事實(shí)也是如此,我們項(xiàng)目中大部分元素都可以復(fù)用,只有 v-for 生成的子元素,它依賴的數(shù)組可能發(fā)生一些較復(fù)雜的變化,所以才需要明確標(biāo)注 key 值,以幫助 Vue 盡可能地復(fù)用節(jié)點(diǎn)。

patchVnode 的內(nèi)容當(dāng)然不止這些,還有樣式、類名、props等數(shù)據(jù)的對(duì)比更換,篇幅原因本文將其省略了。

updateChildren

為什么采用雙端 diff

好了,Vue 的使用者為每個(gè)節(jié)點(diǎn)的設(shè)置了 key,我們要如何從老節(jié)點(diǎn)中找到 key 相等的節(jié)點(diǎn)復(fù)用元素呢?

簡(jiǎn)單的方式就是窮舉遍歷,對(duì)于每個(gè)新節(jié)點(diǎn)的 key 遍歷所有老節(jié)點(diǎn),找到了就移動(dòng)到首位,沒(méi)找到就創(chuàng)建添加。

然而這明顯有優(yōu)化的空間,Vue 實(shí)現(xiàn)這部分功能時(shí)借鑒了 snabbdom 的雙端 diff 算法,因?yàn)榇怂惴▽⑽覀兤綍r(shí)操作數(shù)組常見(jiàn)的 4 種情況抽離了出來(lái),涵蓋了我們業(yè)務(wù)中的大多數(shù)場(chǎng)景,將 O(n2) 的時(shí)間復(fù)雜度降到了 O(n)

接下來(lái)我們來(lái)學(xué)習(xí)這是如何實(shí)現(xiàn)的

函數(shù)實(shí)現(xiàn)

函數(shù)實(shí)現(xiàn)較為復(fù)雜,我直接把完整的代碼放上來(lái),再帶領(lǐng)大家一段段解讀

// 三個(gè)參數(shù)為:父DOM元素,舊的子節(jié)點(diǎn)數(shù)組,新的子節(jié)點(diǎn)數(shù)組
function updateChildren(parentElm, oldCh, newCh) {
    // 舊前索引
    let oldStartIdx = 0
    // 新前索引
    let newStartIdx = 0
    // 舊后索引
    let oldEndIdx = oldCh.length - 1
    // 新后索引
    let newEndIdx = newCh.length - 1
    // 四個(gè)索引對(duì)應(yīng)節(jié)點(diǎn)
    let oldStartVnode = oldCh[0]
    let newStartVnode = newCh[0]
    let oldEndVnode = oldCh[oldEndIdx]
    let newEndVnode = newCh[newEndIdx]

    let keyMap

    // 開始循環(huán)
    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
        // 跳過(guò)空節(jié)點(diǎn) (和最后一種情況有關(guān))
        if (isUndef(oldStartVnode)) {
            oldStartVnode = oldCh[++oldStartIdx]
        } else if (isUndef(oldEndVnode)) {
            oldEndVnode = oldCh[--oldEndIdx]
        } else if (checkSameVnode(oldStartVnode, newStartVnode)) {
            // 情況1
            // 舊前和新前相等,不需要移動(dòng)
            patchVnode(newStartVnode, oldStartVnode)
            oldStartVnode = oldCh[++oldStartIdx]
            newStartVnode = newCh[++newStartIdx]
        } else if (checkSameVnode(oldEndVnode, newEndVnode)) {
            // 情況2
            // 舊后和新后相等,也不需要移動(dòng)
            patchVnode(newEndVnode, oldEndVnode)
            oldEndVnode = oldCh[--oldEndIdx]
            newEndVnode = newCh[--newEndIdx]
        } else if (checkSameVnode(oldStartVnode, newEndVnode)) {
            // 情況3
            // 舊前和新后相等
            // 舊序列的第一個(gè)節(jié)點(diǎn),變成了新序列的最后一個(gè)節(jié)點(diǎn)
            // 需要將這個(gè)節(jié)點(diǎn)移動(dòng)到舊序列最后一個(gè)節(jié)點(diǎn)的后面
            // 也就是最后一個(gè)節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)的前面
            parentElm.insertBefore(oldStartVnode.elm, oldEndVnode.elm.nextSibling)
            patchVnode(newEndVnode, oldStartVnode)
            oldStartVnode = oldCh[++oldStartIdx]
            newEndVnode = newCh[--newEndIdx]
        } else if (checkSameVnode(oldEndVnode, newStartVnode)) {
            // 情況4
            // 舊后和新前相等
            // 舊序列的最后一個(gè)節(jié)點(diǎn),變成了新序列的第一個(gè)節(jié)點(diǎn)
            // 需要將這個(gè)節(jié)點(diǎn)移動(dòng)到舊序列第一個(gè)節(jié)點(diǎn)的前面
            parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm)
            patchVnode(newStartVnode, oldEndVnode)
            oldEndVnode = oldCh[--oldEndIdx]
            newStartVnode = newCh[++newStartIdx]
        } else {
            // 以上四種情況都不符合
            // 制作舊節(jié)點(diǎn)key的映射對(duì)象
            // 鍵為 key,值為 索引
            if (!keyMap) {
                keyMap = {}
                for (let i = oldStartIdx; i <= oldEndIdx; i++) {
                    keyMap[oldCh[i].key] = i
                }
            }
            // 尋找當(dāng)前新節(jié)點(diǎn)在keyMap中映射的位置序號(hào)
            const idxInOld = keyMap[newStartVnode.key]
            if (isUndef(idxInOld)) {
                // 沒(méi)有找到,表示他是全新的項(xiàng)
                // 轉(zhuǎn)化為DOM節(jié)點(diǎn),加入舊序列第一個(gè)節(jié)點(diǎn)的前面
                parentElm.insertBefore(createElement(newStartVnode), oldStartVnode.elm)
            } else {
                // 不是全新的項(xiàng),需要移動(dòng)
                const oldVnode = oldCh[idxInOld]
                // 移動(dòng)到舊序列第一個(gè)節(jié)點(diǎn)之前
                parentElm.insertBefore(oldVnode.elm, oldStartVnode.elm)
                patchVnode(oldVnode, newStartVnode)
                // 把這項(xiàng)設(shè)置成空,循環(huán)時(shí)遇到時(shí)跳過(guò)
                oldCh[idxInOld] = undefined
            }
            // 當(dāng)前新節(jié)點(diǎn)處理完畢,下一輪循環(huán)處理下一個(gè)新節(jié)點(diǎn)
            newStartVnode = newCh[++newStartIdx]
        }
    }

    // 循環(huán)結(jié)束了,start還是比end小,說(shuō)明有節(jié)點(diǎn)沒(méi)有處理到
    if (newStartIdx <= newEndIdx) {
        // 新節(jié)點(diǎn)沒(méi)有處理到,則創(chuàng)建按DOM添加到新序列最后一個(gè)節(jié)點(diǎn)的前面
        for (let i = newStartIdx; i <= newEndIdx; i++) {
            // insertBefore方法傳入null則添加到隊(duì)尾
            const before = newCh[newEndIdx + 1]?.elm || null
            parentElm.insertBefore(createElement(newCh[i]), before)
        }
    } else if (oldStartIdx <= oldEndIdx) {
        // 舊節(jié)點(diǎn)沒(méi)有處理到,刪除
        for (let i = oldStartIdx; i <= oldEndIdx; i++) {
            parentElm.removeChild(oldCh[i].elm)
        }
    }
}

代碼注釋中及下文的新/舊序列,僅包含從新/舊開始索引到結(jié)束索引間的節(jié)點(diǎn),也就是還未處理的節(jié)點(diǎn)序列,而不是整個(gè)子節(jié)點(diǎn)數(shù)組。

根據(jù)例子講解

我們以下圖的例子,來(lái)講解這個(gè)函數(shù)的運(yùn)行流程(方框中的內(nèi)容為子節(jié)點(diǎn)的 key,所有節(jié)點(diǎn)標(biāo)簽相同)

首先定義了 8 個(gè)變量,表示新舊序列的開始和結(jié)束位置的索引與節(jié)點(diǎn)

Vue2中的雙端diff算法怎么更新節(jié)點(diǎn)

然后開始循環(huán),初始時(shí)節(jié)點(diǎn)都不為空

第一次循環(huán)命中情況 1,舊前與新前(key)相等

這表示舊序列的第一個(gè)節(jié)點(diǎn)到新序列仍是第一個(gè)節(jié)點(diǎn),也就不需要移動(dòng),但還需要比較一下節(jié)點(diǎn)的內(nèi)容有沒(méi)有改變(patchVnode),并且讓新舊開始索引都前進(jìn)一步

// 比較節(jié)點(diǎn)的數(shù)據(jù)及子節(jié)點(diǎn),并且將舊節(jié)點(diǎn)的DOM賦給新節(jié)點(diǎn)
patchVnode(newStartVnode, oldStartVnode)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]

Vue2中的雙端diff算法怎么更新節(jié)點(diǎn)

情況 1 是業(yè)務(wù)中最常見(jiàn)的,表示從前至后兩兩比較。一般把商品添加到購(gòu)物車末尾,或是沒(méi)有設(shè)置 key 值的子節(jié)點(diǎn),都是依靠情況 1 把可復(fù)用的節(jié)點(diǎn)篩選完畢。

第二次循環(huán)命中情況 2,舊后和新后相等

這表示序列的末尾節(jié)點(diǎn)到新序列仍是末尾節(jié)點(diǎn),也不需要移動(dòng),然后讓新舊結(jié)束索引都后退一步

patchVnode(newEndVnode, oldEndVnode)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]

Vue2中的雙端diff算法怎么更新節(jié)點(diǎn)

情況 2 是情況 1 的補(bǔ)充,表示從后向前兩兩比較。有時(shí)會(huì)把新發(fā)布的評(píng)論插到開頭,或者從購(gòu)物車刪除了一些商品,這時(shí)僅依靠情況 1 就無(wú)法迅速的篩選可復(fù)用節(jié)點(diǎn),所以需要從后向前比較來(lái)配合。

第三次循環(huán)命中情況 3,舊前和新后相等

這表示舊序列的第一個(gè)節(jié)點(diǎn),變成了新序列的最后一個(gè)節(jié)點(diǎn)。需要將這個(gè)節(jié)點(diǎn)移動(dòng)到序列的末尾,也就是舊序列末尾節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)(節(jié)點(diǎn) e)的前面

parentElm.insertBefore(oldStartVnode.elm, oldEndVnode.elm.nextSibling)

Vue2中的雙端diff算法怎么更新節(jié)點(diǎn)

然后比較新舊節(jié)點(diǎn),修改索引

patchVnode(newEndVnode, oldStartVnode)
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]

Vue2中的雙端diff算法怎么更新節(jié)點(diǎn)

情況 3 主要處理數(shù)組反轉(zhuǎn)的情況,比如升序改降序,每個(gè)起始節(jié)點(diǎn)被移動(dòng)到了末尾的位置,使用此情況將它們重新排序。

第四次循環(huán)命中情況 4,舊后與新前相等

這表示舊序列的最后一個(gè)節(jié)點(diǎn),變成了新序列的第一個(gè)節(jié)點(diǎn)。需要將這個(gè)節(jié)點(diǎn)移動(dòng)到序列的開頭,也就是舊序列開始節(jié)點(diǎn)(節(jié)點(diǎn) c)的前面

parentElm.insertBefore(oldStartVnode.elm, oldEndVnode.elm.nextSibling)

Vue2中的雙端diff算法怎么更新節(jié)點(diǎn)

到這里說(shuō)一下,圖上標(biāo)注的是節(jié)點(diǎn) a 的后面,是因?yàn)楣?jié)點(diǎn) b 被移動(dòng)到了末尾

節(jié)點(diǎn)的移動(dòng)都是根據(jù)舊節(jié)點(diǎn)來(lái)定位的,如果想把一個(gè)節(jié)點(diǎn)放到序列的開頭,就放到舊序列開始節(jié)點(diǎn)的前面;如果想把一個(gè)節(jié)點(diǎn)放到序列的末尾,就要放到舊序列結(jié)束節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)的前面

然后也是比較新舊節(jié)點(diǎn),修改索引,之后是下圖情況

patchVnode(newStartVnode, oldEndVnode)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]

Vue2中的雙端diff算法怎么更新節(jié)點(diǎn)

情況 4 是情況 3 的補(bǔ)充,避免反轉(zhuǎn)數(shù)組后又插入/刪除了節(jié)點(diǎn)導(dǎo)致情況 3 無(wú)法匹配,本例就是這個(gè)情況。

第五次循環(huán),4 種情況均為未命中

很遺憾,無(wú)法迅速鎖定節(jié)點(diǎn)的位置,只能用傳統(tǒng)的方式進(jìn)行遍歷

我們這里選擇了以空間換時(shí)間的方式,定義了 keyMap,將舊序列節(jié)點(diǎn)的 key 與索引存起來(lái),然后使用新開始節(jié)點(diǎn)的 key 去查找。

如果沒(méi)找到,說(shuō)明這是一個(gè)新節(jié)點(diǎn),創(chuàng)建節(jié)點(diǎn)并放到開頭,也就是插入到舊序列開始節(jié)點(diǎn)的前面

但如果找到了,則同樣移動(dòng)節(jié)點(diǎn)到序列開頭,然后將對(duì)應(yīng)的舊節(jié)點(diǎn)索引置空,在以后循環(huán)遇到空的舊節(jié)點(diǎn)就跳過(guò)了

本例中是未找到的情況,此節(jié)點(diǎn)處理完畢,新開始索引加一,超過(guò)了新結(jié)束索引,不滿足循環(huán)條件,退出循環(huán)

Vue2中的雙端diff算法怎么更新節(jié)點(diǎn)

然而,節(jié)點(diǎn) c 并沒(méi)有被處理,此時(shí)的 DOM 序列為:a,d,f,c,b,e

所以在循環(huán)之后,要檢測(cè)是否有未處理的節(jié)點(diǎn),如果是舊節(jié)點(diǎn)未處理,刪除即可;

如果是新節(jié)點(diǎn)未處理,則創(chuàng)建新節(jié)點(diǎn)插入到舊序列的末尾或者舊序列的開頭,二者其實(shí)是一個(gè)位置

我們假設(shè)舊節(jié)點(diǎn)中沒(méi)有 c,則在第四次循環(huán)后就會(huì)出現(xiàn)以下情況(第四次循環(huán)命中的是情況 1)

Vue2中的雙端diff算法怎么更新節(jié)點(diǎn)

如果把 f 放到序列的開頭,就是舊開始節(jié)點(diǎn)(節(jié)點(diǎn) e)的前面

而如果把 f 放到序列的末尾,也就是舊結(jié)束節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)(節(jié)點(diǎn) e)的前面

此時(shí)舊序列就是一個(gè)點(diǎn),不分開頭和結(jié)尾,只要保證新增節(jié)點(diǎn)序列按序添加就好了

至此,雙端 diff 算法就講完了

Vue 中的 key

學(xué)完 diff 算法,再聊聊 key 的作用

v-for 中的 key

上面講的都是有 key 情況下,diff 算法能夠迅速找到新舊序列中的同一節(jié)點(diǎn),以較小的代價(jià)完成更新。

而如果在 v-for 中不設(shè)置 key 呢?

假設(shè)我們?cè)跀?shù)組頭部插入了一個(gè)新節(jié)點(diǎn),然后開始循環(huán),每次循環(huán)都命中情況 1,嘗試“復(fù)用”此節(jié)點(diǎn)。

但是,在對(duì)比新舊節(jié)點(diǎn)的內(nèi)容時(shí),都會(huì)發(fā)現(xiàn)內(nèi)容不同,需要用新節(jié)點(diǎn)的內(nèi)容替換舊節(jié)點(diǎn)。這只是復(fù)用了 DOM 的外殼,節(jié)點(diǎn)的內(nèi)容、數(shù)據(jù)以及該節(jié)點(diǎn)的子節(jié)點(diǎn)全都要更改。

相比有 key 時(shí)的只添加一個(gè)新節(jié)點(diǎn),無(wú) key 則將所有節(jié)點(diǎn)都修改一遍。

v-if 自帶 key

v-for 以外的元素我們一般是不設(shè)置 key 的,但是如果子元素中有 v-if 的話,就像下面這個(gè)場(chǎng)景(abcd是內(nèi)容,并不是 key),更新子元素又會(huì)復(fù)現(xiàn)上一節(jié)的情況。

Vue2中的雙端diff算法怎么更新節(jié)點(diǎn)

然而 Vue 官方也考慮到了這點(diǎn),會(huì)為 v-if 的元素加上利用 hash 函數(shù)生成的唯一 key

// 以下出自 v2 源碼
var needsKey = !!el.if 
……
needsKey ? ',null,false,' + hash(generatedSlots) : ''

key 的另一個(gè)用法

順便再提一嘴,key 可以綁到任意元素上,當(dāng) key 發(fā)生變化時(shí),會(huì)導(dǎo)致 DOM 的銷毀與重建,一般用來(lái)重復(fù)觸發(fā)動(dòng)畫或生命周期鉤子。

關(guān)于“Vue2中的雙端diff算法怎么更新節(jié)點(diǎn)”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對(duì)“Vue2中的雙端diff算法怎么更新節(jié)點(diǎn)”知識(shí)都有一定的了解,大家如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI