溫馨提示×

溫馨提示×

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

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

Vue2 diff算法怎么掌握

發(fā)布時(shí)間:2023-03-20 09:16:22 來源:億速云 閱讀:122 作者:iii 欄目:編程語言

今天小編給大家分享一下Vue2 diff算法怎么掌握的相關(guān)知識(shí)點(diǎn),內(nèi)容詳細(xì),邏輯清晰,相信大部分人都還太了解這方面的知識(shí),所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

什么是 diff ?

在我的理解中,diff 指代的是 differences,即 新舊內(nèi)容之間的區(qū)別計(jì)算;Vue 中的 diff 算法,則是通過一種 簡單且高效 的手段快速對比出 新舊 VNode 節(jié)點(diǎn)數(shù)組之間的區(qū)別 以便 以最少的 dom 操作來更新頁面內(nèi)容。

此時(shí)這里有兩個(gè)必須的前提:

  • 對比的是 VNode 數(shù)組

  • 同時(shí)存在新舊兩組 VNode 數(shù)組

所以它一般只發(fā)生在 數(shù)據(jù)更新造成頁面內(nèi)容需要更新時(shí)執(zhí)行,即 renderWatcher.run()。

為什么是 VNode ?

上面說了,diff 中比較的是 VNode,而不是真實(shí)的 dom 節(jié)點(diǎn),相信為什么會(huì)使用 VNode 大部分人都比較清楚,筆者就簡單帶過吧?~

在 Vue 中使用 VNode 的原因大致有兩個(gè)方面:

  • VNode 作為框架設(shè)計(jì)者根據(jù)框架需求設(shè)計(jì)的 JavaScript 對象,本身屬性相對真實(shí)的 dom 節(jié)點(diǎn)要簡單,并且操作時(shí)不需要進(jìn)行 dom 查詢,可以大幅優(yōu)化計(jì)算時(shí)的性能消耗

  • 在 VNode 到真實(shí) dom 的這個(gè)渲染過程,可以根據(jù)不同平臺(tái)(web、微信小程序)進(jìn)行不同的處理,生成適配各平臺(tái)的真實(shí) dom 元素

在 diff 過程中會(huì)遍歷新舊節(jié)點(diǎn)數(shù)據(jù)進(jìn)行對比,所以使用 VNode 能帶來很大的性能提升。

流程梳理

在網(wǎng)頁中,真實(shí)的 dom 節(jié)點(diǎn)都是以 的形式存在的,根節(jié)點(diǎn)都是 <html>,為了保證虛擬節(jié)點(diǎn)能與真實(shí) dom 節(jié)點(diǎn)一致,VNode 也一樣采用的是樹形結(jié)構(gòu)。

如果在組件更新時(shí),需要對比全部 VNode 節(jié)點(diǎn)的話,新舊兩組節(jié)點(diǎn)都需要進(jìn)行 深度遍歷 和比較,會(huì)產(chǎn)生很大的性能開銷;所以,Vue 中默認(rèn) 同層級節(jié)點(diǎn)比較,即 如果新舊 VNode 樹的層級不同的話,多余層級的內(nèi)容會(huì)直接新建或者舍棄,只在同層級進(jìn)行 diff 操作。

一般來說,diff 操作一般發(fā)生在 v-for 循環(huán)或者有 v-if/v-else 、component 這類 動(dòng)態(tài)生成 的節(jié)點(diǎn)對象上(靜態(tài)節(jié)點(diǎn)一般不會(huì)改變,對比起來很快),并且這個(gè)過程是為了更新 dom,所以在源碼中,這個(gè)過程對應(yīng)的方法名是 updateChildren,位于 src/core/vdom/patch.ts 中。如下圖:

Vue2 diff算法怎么掌握

這里回顧一下 Vue 組件實(shí)例的創(chuàng)建與更新過程:

  • 首先是 beforeCreatecreated 階段,主要進(jìn)行數(shù)據(jù)和狀態(tài)以及一些基礎(chǔ)事件、方法的處理

  • 然后,會(huì)調(diào)用 $mount(vm.$options.el) 方法進(jìn)入 Vnode 與 dom 的創(chuàng)建和掛載階段,也就是 beforeMountmounted 之間(組件更新時(shí)與這里類似)

  • 原型上的 $mount 會(huì)在 platforms/web/runtime-with-compiler.ts 中進(jìn)行一次重寫,原始實(shí)現(xiàn)在 platforms/web/runtime/index.ts 中;在原始實(shí)現(xiàn)方法中,其實(shí)就是調(diào)用 mountComponent 方法執(zhí)行 render;而在 web 下的 runtime-with-compiler 中則是增加了 模板字符串編譯 模塊,會(huì)對 options 中的的 template 進(jìn)行一次解析和編譯,轉(zhuǎn)換成一個(gè)函數(shù)綁定到 options.render

  • mountComponent 函數(shù)內(nèi)部就是 定義了渲染方法 updateComponent = () => (vm._update(vm._render()),實(shí)例化一個(gè)具有 before 配置的 watcher 實(shí)例(即 renderWatcher),通過定義 watch 觀察對象為 剛剛定義的 updateComponent 方法來執(zhí)行 首次組件渲染與觸發(fā)依賴收集,其中的 before 配置僅僅配置了觸發(fā) beforeMount/beforeUpdate 鉤子函數(shù)的方法;這也是為什么在 beforeMount 階段取不到真實(shí) dom 節(jié)點(diǎn)與 beforeUpdate 階段獲取的是舊 dom 節(jié)點(diǎn)的原因

  • _update 方法的定義與 mountComponent 在同一文件下,其核心就是 讀取組件實(shí)例中的 $el(舊 dom 節(jié)點(diǎn))與 _vnode(舊 VNode)與 _render() 函數(shù)生成的 vnode 進(jìn)行 patch 操作

  • patch 函數(shù)首先對比 是否具有舊節(jié)點(diǎn),沒有的話肯定是新建的組件,直接進(jìn)行創(chuàng)建和渲染;如果具有舊節(jié)點(diǎn)的話,則通過 patchVnode 進(jìn)行新舊節(jié)點(diǎn)的對比,并且如果新舊節(jié)點(diǎn)一致并且都具有 children 子節(jié)點(diǎn),則進(jìn)入 diff 的核心邏輯 —— updateChildren 子節(jié)點(diǎn)對比更新,這個(gè)方法也是我們常說的 diff 算法

前置內(nèi)容

既然是對比新舊 VNode 數(shù)組,那么首先肯定有 對比 的判斷方法:sameNode(a, b)、新增節(jié)點(diǎn)的方法 addVnodes、移除節(jié)點(diǎn)的方法 removeVnodes,當(dāng)然,即使 sameNode 判斷了 VNode 一致之后,依然會(huì)使用 patchVnode 對單個(gè)新舊 VNode 的內(nèi)容進(jìn)行深度比較,確認(rèn)內(nèi)部數(shù)據(jù)是否需要更新。

sameNode(a, b)

這個(gè)方法就一個(gè)目的:比較新舊節(jié)點(diǎn)是否相同。

在這個(gè)方法中,首先比較的就是 a 和 b 的 key 是否相同,這也是為什么 Vue 在文檔中注明了 v-for、v-if、v-else 等動(dòng)態(tài)節(jié)點(diǎn)必須要設(shè)置 key 來標(biāo)識(shí)節(jié)點(diǎn)唯一性,如果 key 存在且相同,則只需要比較內(nèi)部是否發(fā)生了改變,一般情況下可以減少很多 dom 操作;而如果沒有設(shè)置的話,則會(huì)直接銷毀重建對應(yīng)的節(jié)點(diǎn)元素。

然后會(huì)比較是不是異步組件,這里會(huì)比較他們的構(gòu)造函數(shù)是不是一致。

然后會(huì)進(jìn)入兩種不同的情況比較:

  • 非異步組件:標(biāo)簽一樣、都不是注釋節(jié)點(diǎn)、都有數(shù)據(jù)、同類型文本輸入框

  • 異步組件:舊節(jié)點(diǎn)占位符和新節(jié)點(diǎn)的錯(cuò)誤提示都為 undefined

函數(shù)整體過程如下

Vue2 diff算法怎么掌握

addVnodes

顧名思義,添加新的 VNode 節(jié)點(diǎn)。

該函數(shù)接收 6 個(gè)參數(shù):parentElm 當(dāng)前節(jié)點(diǎn)數(shù)組父元素、refElm 指定位置的元素、vnodes 新的虛擬節(jié)點(diǎn)數(shù)組、startIdx 新節(jié)點(diǎn)數(shù)組的插入元素開始位置、endIdx 新節(jié)點(diǎn)數(shù)組的插入元素結(jié)束索引、insertedVnodeQueue 需要插入的虛擬節(jié)點(diǎn)隊(duì)列。

函數(shù)內(nèi)部會(huì) startIdx 開始遍歷 vnodes 數(shù)組直到 endIdx 位置,然后調(diào)用 createElm 依次在 refElm 之前創(chuàng)建和插入 vnodes[idx] 對應(yīng)的元素。

當(dāng)然,在這個(gè) vnodes[idx] 中有可能會(huì)有 Component 組件,此時(shí)還會(huì)調(diào)用 createComponent 來創(chuàng)建對應(yīng)的組件實(shí)例。

因?yàn)檎麄€(gè) VNode 和 dom 都是一個(gè) 樹結(jié)構(gòu),所以在 同層級的比較之后,還需要處理當(dāng)前層級下更深層次的 VNode 和 dom 處理

removeVnodes

addVnodes 相反,該方法就是用來移除 VNode 節(jié)點(diǎn)的。

由于這個(gè)方法只是移除,所以只需要三個(gè)參數(shù):vnodes 舊虛擬節(jié)點(diǎn)數(shù)組、startIdx 開始索引、endIdx 結(jié)束索引。

函數(shù)內(nèi)部會(huì) startIdx 開始遍歷 vnodes 數(shù)組直到 endIdx 位置,如果 vnodes[idx] 不為 undefined 的話,則會(huì)根據(jù) tag 屬性來區(qū)分處理:

  • 存在 tag,說明是一個(gè)元素或者組件,需要 遞歸處理 vnodes[idx] 的內(nèi)容, 觸發(fā) remove hooks 與 destroy hooks

  • 不存在 tag,說明是一個(gè) 純文本節(jié)點(diǎn),直接從 dom 中移除該節(jié)點(diǎn)即可

patchVnode

節(jié)點(diǎn)對比的 實(shí)際完整對比和 dom 更新 方法。

在這個(gè)方法中,主要包含 九個(gè) 主要的參數(shù)判斷,并對應(yīng)不同的處理邏輯:

  • 新舊 VNode 全等,則說明沒有變化,直接退出

  • 如果新的 VNode 具有真實(shí)的 dom 綁定,并且需要更新的節(jié)點(diǎn)集合是一個(gè)數(shù)組的話,則拷貝當(dāng)前的 VNode 到集合的指定位置

  • 如果舊節(jié)點(diǎn)是一個(gè) 異步組件并且還沒有加載結(jié)束的話就直接退出,否則通過 hydrate 函數(shù)將新的 VNode 轉(zhuǎn)化為真實(shí)的 dom 進(jìn)行渲染;兩種情況都會(huì) 退出該函數(shù)

  • 如果新舊節(jié)點(diǎn)都是 靜態(tài)節(jié)點(diǎn) 并且 key 相等,或者是 isOnce 指定的不更新節(jié)點(diǎn),也會(huì)直接 復(fù)用舊節(jié)點(diǎn)的組件實(shí)例退出函數(shù)

  • 如果新的 VNode 節(jié)點(diǎn)具有 data 屬性并且有配置 prepatch 鉤子函數(shù),則執(zhí)行 prepatch(oldVnode, vnode) 通知進(jìn)入節(jié)點(diǎn)的對比階段,一般這一步會(huì)配置性能優(yōu)化

  • 如果新的 VNode 具有 data 屬性并且遞歸改節(jié)點(diǎn)的子組件實(shí)例的 vnode,依然是可用標(biāo)簽的話,cbs 回調(diào)函數(shù)對象中配置的 update 鉤子函數(shù)以及 data 中配置的 update 鉤子函數(shù)

  • 如果新的 VNode 不是文本節(jié)點(diǎn)的話,會(huì)進(jìn)入核心對比階段

    • 如果新舊節(jié)點(diǎn)都有 children 子節(jié)點(diǎn),則進(jìn)入 updateChildren 方法對比子節(jié)點(diǎn)

    • 如果舊節(jié)點(diǎn)沒有子節(jié)點(diǎn)的話,則直接創(chuàng)建 VNode 對應(yīng)的新的子節(jié)點(diǎn)

    • 如果新節(jié)點(diǎn)沒有子節(jié)點(diǎn)的話,則移除舊的 VNode 子節(jié)點(diǎn)

    • 如果都沒有子節(jié)點(diǎn)的話,并且舊節(jié)點(diǎn)有文本內(nèi)容配置,則清空以前的 text 文本

  • 如果新的 VNode 具有 text 文本(是文本節(jié)點(diǎn)),則比較新舊節(jié)點(diǎn)的文本內(nèi)容是否一致,否則進(jìn)行文本內(nèi)容的更新

  • 最后調(diào)用新節(jié)點(diǎn)的 data 中配置的 postpatch 鉤子函數(shù),通知節(jié)點(diǎn)更新完畢

簡單來說,patchVnode 就是在 同一個(gè)節(jié)點(diǎn) 更新階段 進(jìn)行新內(nèi)容與舊內(nèi)容的對比,如果發(fā)生改變則更新對應(yīng)的內(nèi)容;如果有子節(jié)點(diǎn),則“遞歸”執(zhí)行每個(gè)子節(jié)點(diǎn)的比較和更新。

子節(jié)點(diǎn)數(shù)組的比較和更新,則是 diff 的核心邏輯,也是面試時(shí)經(jīng)常被提及的問題之一。

下面,就進(jìn)入 updateChildren 方法的解析吧~

updateChildren diff 核心解析

首先,我們先思考一下 以新數(shù)組為準(zhǔn)比較兩個(gè)對象數(shù)組元素差異 有哪些方法?

一般來說,我們可以通過 暴力手段直接遍歷兩個(gè)數(shù)組 來查找數(shù)組中每個(gè)元素的順序和差異,也就是 簡單 diff 算法。

遍歷新節(jié)點(diǎn)數(shù)組,在每次循環(huán)中再次遍歷舊節(jié)點(diǎn)數(shù)組 對比兩個(gè)節(jié)點(diǎn)是否一致,通過對比結(jié)果確定新節(jié)點(diǎn)是新增還是移除還是移動(dòng),整個(gè)過程中需要進(jìn)行 m*n 次比較,所以默認(rèn)時(shí)間復(fù)雜度是 On。

這種比較方式在大量節(jié)點(diǎn)更新過程中是非常消耗性能的,所以 Vue 2 對其進(jìn)行了優(yōu)化,改為 雙端對比算法,也就是 雙端 diff。

雙端 diff 算法

顧名思義,雙端 就是 從兩端開始分別向中間進(jìn)行遍歷對比 的算法。

雙端 diff 中,分為 五種比較情況

  • 新舊頭相等

  • 新舊尾相等

  • 舊頭等于新尾

  • 舊尾等于新頭

  • 四者互不相等

其中,前四種屬于 比較理想的情況,而第五種才是 最復(fù)雜的對比情況。

判斷相等即 sameVnode(a, b) 等于 true

下面我們通過一種預(yù)設(shè)情況來進(jìn)行分析。

1. 預(yù)設(shè)新舊節(jié)點(diǎn)狀態(tài)

為了盡量同時(shí)演示出以上五種情況,我預(yù)設(shè)了以下的新舊節(jié)點(diǎn)數(shù)組:

  • 作為初始節(jié)點(diǎn)順序的舊節(jié)點(diǎn)數(shù)組 oldChildren,包含 1 - 7 共 7 個(gè)節(jié)點(diǎn)

  • 作為亂序后的新節(jié)點(diǎn)數(shù)組 newChildren,也有 7 個(gè)節(jié)點(diǎn),但是相比舊節(jié)點(diǎn)減少了一個(gè) vnode 3 并增加了一個(gè) vnode 8

在進(jìn)行比較之前,首先需要 定義兩組節(jié)點(diǎn)的雙端索引

let oldStartIdx = 0
let oldEndIdx = oldCh.length - 1
let oldStartVnode = oldCh[0]
let oldEndVnode = oldCh[oldEndIdx]

let newStartIdx = 0
let newEndIdx = newCh.length - 1
let newStartVnode = newCh[0]
let newEndVnode = newCh[newEndIdx]

復(fù)制的源代碼,其中 oldCh 在圖中為 oldChildren,newChnewChildren

然后,我們定義 遍歷對比操作的停止條件

while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx)

這里的停止條件是 只要新舊節(jié)點(diǎn)數(shù)組任意一個(gè)遍歷結(jié)束,則立即停止遍歷。

此時(shí)節(jié)點(diǎn)狀態(tài)如下:

Vue2 diff算法怎么掌握

2. 確認(rèn) vnode 存在才進(jìn)行對比

為了保證新舊節(jié)點(diǎn)數(shù)組在對比時(shí)不會(huì)進(jìn)行無效對比,會(huì)首先排除掉舊節(jié)點(diǎn)數(shù)組 起始部分與末尾部分 連續(xù)且值為 Undefined 的數(shù)據(jù)。

if (isUndef(oldStartVnode)) {
  oldStartVnode = oldCh[++oldStartIdx]
} else if (isUndef(oldEndVnode)) {
  oldEndVnode = oldCh[--oldEndIdx]

Vue2 diff算法怎么掌握

當(dāng)然我們的例子中沒有這種情況,可以忽略。

3. 舊頭等于新頭

此時(shí)相當(dāng)于新舊節(jié)點(diǎn)數(shù)組的兩個(gè) 起始索引 指向的節(jié)點(diǎn)是 基本一致的,那么此時(shí)會(huì)調(diào)用 patchVnode 對兩個(gè) vnode 進(jìn)行深層比較和 dom 更新,并且將 兩個(gè)起始索引向后移動(dòng)。即:

if (sameVnode(oldStartVnode, newStartVnode)) {
  patchVnode(
    oldStartVnode,
    newStartVnode,
    insertedVnodeQueue,
    newCh,
    newStartIdx
  )
  oldStartVnode = oldCh[++oldStartIdx]
  newStartVnode = newCh[++newStartIdx]
}

這時(shí)的節(jié)點(diǎn)和索引變化如圖所示:

Vue2 diff算法怎么掌握

4. 舊尾等于新尾

與頭結(jié)點(diǎn)相等類似,這種情況代表 新舊節(jié)點(diǎn)數(shù)組的最后一個(gè)節(jié)點(diǎn)基本一致,此時(shí)一樣調(diào)用 patchVnode 比較兩個(gè)尾結(jié)點(diǎn)和更新 dom,然后將 兩個(gè)末尾索引向前移動(dòng)。

if (sameVnode(oldEndVnode, newEndVnode)) {
  patchVnode(
    oldEndVnode,
    newEndVnode,
    insertedVnodeQueue,
    newCh,
    newEndIdx
  )
  oldEndVnode = oldCh[--oldEndIdx]
  newEndVnode = newCh[--newEndIdx]
}

這時(shí)的節(jié)點(diǎn)和索引變化如圖所示:

Vue2 diff算法怎么掌握

5. 舊頭等于新尾

這里表示的是 舊節(jié)點(diǎn)數(shù)組 當(dāng)前起始索引 指向的 vnode 與 新節(jié)點(diǎn)數(shù)組 當(dāng)前末尾索引 指向的 vnode 基本一致,一樣調(diào)用 patchVnode 對兩個(gè)節(jié)點(diǎn)進(jìn)行處理。

但是與上面兩種有區(qū)別的地方在于:這種情況下會(huì)造成 節(jié)點(diǎn)的移動(dòng),所以此時(shí)還會(huì)在 patchVnode 結(jié)束之后 通過 nodeOps.insertBefore舊的頭節(jié)點(diǎn) 重新插入到 當(dāng)前 舊的尾結(jié)點(diǎn)之后

然后,會(huì)將 舊節(jié)點(diǎn)的起始索引后移、新節(jié)點(diǎn)的末尾索引前移。

看到這里大家可能會(huì)有一個(gè)疑問,為什么這里移動(dòng)的是 舊的節(jié)點(diǎn)數(shù)組,這里因?yàn)?vnode 節(jié)點(diǎn)中有一個(gè)屬性 elm,會(huì)指向該 vnode 對應(yīng)的實(shí)際 dom 節(jié)點(diǎn),所以這里移動(dòng)舊節(jié)點(diǎn)數(shù)組其實(shí)就是 側(cè)面去移動(dòng)實(shí)際的 dom 節(jié)點(diǎn)順序;并且注意這里是 當(dāng)前的尾結(jié)點(diǎn),在索引改變之后,這里不一定就是原舊節(jié)點(diǎn)數(shù)組的最末尾

即:

if (sameVnode(oldStartVnode, newEndVnode)) {
  patchVnode(
    oldStartVnode,
    newEndVnode,
    insertedVnodeQueue,
    newCh,
    newEndIdx
  )
  canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
  oldStartVnode = oldCh[++oldStartIdx]
  newEndVnode = newCh[--newEndIdx]
}

此時(shí)狀態(tài)如下:

Vue2 diff算法怎么掌握

6. 舊尾等于新頭

這里與上面的 舊頭等于新尾 類似,一樣要涉及到節(jié)點(diǎn)對比和移動(dòng),只是調(diào)整的索引不同。此時(shí) 舊節(jié)點(diǎn)的 末尾索引 前移、新節(jié)點(diǎn)的 起始索引 后移,當(dāng)然了,這里的 dom 移動(dòng)對應(yīng)的 vnode 操作是 將舊節(jié)點(diǎn)數(shù)組的末尾索引對應(yīng)的 vnode 插入到舊節(jié)點(diǎn)數(shù)組 起始索引對應(yīng)的 vnode 之前。

if (sameVnode(oldEndVnode, newStartVnode)) {
  patchVnode(
    oldEndVnode,
    newStartVnode,
    insertedVnodeQueue,
    newCh,
    newStartIdx
  )
  canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
  oldEndVnode = oldCh[--oldEndIdx]
  newStartVnode = newCh[++newStartIdx]
}

此時(shí)狀態(tài)如下:

Vue2 diff算法怎么掌握

7. 四者均不相等

在以上情況都處理之后,就來到了四個(gè)節(jié)點(diǎn)互相都不相等的情況,這種情況也是 最復(fù)雜的情況。

當(dāng)經(jīng)過了上面幾種處理之后,此時(shí)的 索引與對應(yīng)的 vnode 狀態(tài)如下:

Vue2 diff算法怎么掌握

可以看到四個(gè)索引對應(yīng)的 vnode 分別是:vnode 3、vnode 5、 vnode 4、vnode 8,這幾個(gè)肯定是不一樣的。

此時(shí)也就意味著 雙端對比結(jié)束。

后面的節(jié)點(diǎn)對比則是 將舊節(jié)點(diǎn)數(shù)組剩余的 vnode (oldStartIdxoldEndIdx 之間的節(jié)點(diǎn))進(jìn)行一次遍歷,生成由 vnode.key 作為鍵,idx 索引作為值的對象 oldKeyToIdx,然后 遍歷新節(jié)點(diǎn)數(shù)組的剩余 vnode(newStartIdxnewEndIdx 之間的節(jié)點(diǎn)),根據(jù)新的節(jié)點(diǎn)的 keyoldKeyToIdx 進(jìn)行查找。此時(shí)的每個(gè)新節(jié)點(diǎn)的查找結(jié)果只有兩種情況:

  • 找到了對應(yīng)的索引,那么會(huì)通過 sameVNode 對兩個(gè)節(jié)點(diǎn)進(jìn)行對比:

    • 相同節(jié)點(diǎn),調(diào)用 patchVnode 進(jìn)行深層對比和 dom 更新,將 oldKeyToIdx 中對應(yīng)的索引 idxInOld 對應(yīng)的節(jié)點(diǎn)插入到 oldStartIdx 對應(yīng)的 vnode 之前;并且,這里會(huì)將 舊節(jié)點(diǎn)數(shù)組中 idxInOld 對應(yīng)的元素設(shè)置為 undefined

    • 不同節(jié)點(diǎn),則調(diào)用 createElm 重新創(chuàng)建一個(gè)新的 dom 節(jié)點(diǎn)并將 新的 vnode 插入到對應(yīng)的位置

  • 沒有找到對應(yīng)的索引,則直接 createElm 創(chuàng)建新的 dom 節(jié)點(diǎn)并將新的 vnode 插入到對應(yīng)位置

注:這里 只有找到了舊節(jié)點(diǎn)并且新舊節(jié)點(diǎn)一樣才會(huì)將舊節(jié)點(diǎn)數(shù)組中 idxInOld 中的元素置為 undefined。

最后,會(huì)將 新節(jié)點(diǎn)數(shù)組的 起始索引 向后移動(dòng)

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

大致邏輯如下圖:

Vue2 diff算法怎么掌握

剩余未比較元素處理

經(jīng)過上面的處理之后,根據(jù)判斷條件也不難看出,遍歷結(jié)束之后 新舊節(jié)點(diǎn)數(shù)組都剛好沒有剩余元素 是很難出現(xiàn)的,當(dāng)且僅當(dāng)遍歷過程中每次新頭尾節(jié)點(diǎn)總能和舊頭尾節(jié)點(diǎn)中總能有兩個(gè)新舊節(jié)點(diǎn)相同時(shí)才會(huì)發(fā)生,只要有一個(gè)節(jié)點(diǎn)發(fā)生改變或者順序發(fā)生大幅調(diào)整,最后 都會(huì)有一個(gè)節(jié)點(diǎn)數(shù)組起始索引和末尾索引無法閉合。

那么此時(shí)就需要對剩余元素進(jìn)行處理:

  • 舊節(jié)點(diǎn)數(shù)組遍歷結(jié)束、新節(jié)點(diǎn)數(shù)組仍有剩余,則遍歷新節(jié)點(diǎn)數(shù)組剩余數(shù)據(jù),分別創(chuàng)建節(jié)點(diǎn)并插入到舊末尾索引對應(yīng)節(jié)點(diǎn)之前

  • 新節(jié)點(diǎn)數(shù)組遍歷結(jié)束、舊節(jié)點(diǎn)數(shù)組仍有剩余,則遍歷舊節(jié)點(diǎn)數(shù)組剩余數(shù)據(jù),分別從節(jié)點(diǎn)數(shù)組和 dom 樹中移除

即:

Vue2 diff算法怎么掌握

以上就是“Vue2 diff算法怎么掌握”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會(huì)為大家更新不同的知識(shí),如果還想學(xué)習(xí)更多的知識(shí),請關(guān)注億速云行業(yè)資訊頻道。

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

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

AI