Vue3快速diff算法的處理過程
一、為什么要使用diff算法
新舊vnode節(jié)點(diǎn)都有一組子節(jié)點(diǎn)的情況下,如果不使用diff算法處理則渲染器的做法是,將舊的子節(jié)點(diǎn)全部卸載,再掛載新的子節(jié)點(diǎn),并沒有考慮到節(jié)點(diǎn)的復(fù)用情況,比如下面的兩組vnode
const newVnode = {
type: 'div',
children: [
{ type: 'p', children: '1' },
{ type: 'p', children: '2' },
{ type: 'p', children: '3' },
]
}
const oldVnode = {
type: 'div',
children: [
{ type: 'p', children: '4' },
{ type: 'p', children: '5' },
{ type: 'p', children: '6' }
]
}
實(shí)際上并不需要去全部卸載然后掛載新的子節(jié)點(diǎn),只需要替換子節(jié)點(diǎn)中p標(biāo)簽中的文本內(nèi)容即可。 Vue使用diff算法的原因就是為了避免全量更新子節(jié)點(diǎn),盡可能的去復(fù)用或者使用較少的操作去完成節(jié)點(diǎn)的更新。
二、如何復(fù)用子節(jié)點(diǎn)
1.判斷是否可復(fù)用:
觀察以下兩個(gè)新舊節(jié)點(diǎn):他們的類型相同都是p元素,并且其內(nèi)容其實(shí)也沒有變化,只是元素的順序發(fā)生了變動(dòng),這種情況我們完全可以復(fù)用新舊節(jié)點(diǎn):
const newVnode = {
type: 'div',
children: [
{ type: 'p', children: '1' },
{ type: 'p', children: '2' },
{ type: 'p', children: '3' },
]
}
const oldVnode = {
type: 'div',
children: [
{ type: 'p', children: '3' },
{ type: 'p', children: '2' },
{ type: 'p', children: '1' }
]
}
為了能夠識(shí)別出哪些子節(jié)點(diǎn)是我們可以復(fù)用的,可以給其加上key屬性,當(dāng)新舊節(jié)點(diǎn)的key值相同時(shí),則證明他們是同一個(gè)子節(jié)點(diǎn),可以復(fù)用。
const newVnode = {
type: 'div',
children: [
{ type: 'p', children: '1', key:1 },
{ type: 'p', children: '2', key:2 },
{ type: 'p', children: '3', key:3 },
]
}
const oldVnode = {
type: 'div',
children: [
{ type: 'p', children: '3', key:3 },
{ type: 'p', children: '2', key:2 },
{ type: 'p', children: '1', key:1 },
]
}

2.對(duì)可復(fù)用節(jié)點(diǎn)的處理:
節(jié)點(diǎn)可復(fù)用并不意味著只需要簡(jiǎn)單的處理新舊子節(jié)點(diǎn)的順序變化,子節(jié)點(diǎn)的內(nèi)容可能也會(huì)發(fā)生變動(dòng),所以在移動(dòng)之前需要打補(bǔ)丁確保內(nèi)容更新:我們需要對(duì)前面處理子節(jié)點(diǎn)更新的patchChildren進(jìn)行完善,主要處理其中新舊子節(jié)點(diǎn)都是多個(gè)的情況,此時(shí)我們才需要使用diff算法處理,其中再使用patch函數(shù)去更新可復(fù)用節(jié)點(diǎn),具體的處理過程在下文中進(jìn)行描述:
function patchChildren(n1, n2, container) {
if (typeof n2.children === 'string') {
//省略代碼
} else if (Array.isArray(n2.children)) {
//新子節(jié)點(diǎn)是一組節(jié)點(diǎn)
if (Array.isArray(n1.children)) {
//舊子節(jié)點(diǎn)也是一組節(jié)點(diǎn),應(yīng)用diff算法處理
//省略diff算法代碼
//diff中會(huì)使用patch去更新可復(fù)用元素
} else if (typeof n1.children === 'string') {
//省略代碼
}
}
}
三、Vue3快速diff算法的處理過程
1.預(yù)處理:處理兩組子節(jié)點(diǎn)中首尾節(jié)點(diǎn)可復(fù)用的情況
比如下面的情況:

有三個(gè)節(jié)點(diǎn)key值相同,可以復(fù)用,并且他們?cè)谧庸?jié)點(diǎn)中的相對(duì)順序也沒有發(fā)生變化,p-1在最前面,p-2和p-3在最后面。所以他們并不需要移動(dòng),只需要處理中間的節(jié)點(diǎn)。
處理前置節(jié)點(diǎn):
設(shè)置一個(gè)索引j從0開始使用while循環(huán)尋找相同的前置節(jié)點(diǎn):如果是key相同的節(jié)點(diǎn),調(diào)用patch函數(shù)打補(bǔ)丁更新其中的內(nèi)容,直到使用同一個(gè)索引取到的新舊子節(jié)點(diǎn)key值不同

處理后置節(jié)點(diǎn):
拿到新舊子節(jié)點(diǎn)最后一個(gè)元素的索引oldEnd和newEnd,使用while從兩組節(jié)點(diǎn)尾部往上遍歷,如果是key相同的節(jié)點(diǎn)則調(diào)用patch函數(shù)打補(bǔ)丁更新其中的內(nèi)容,知道取不到相同key的節(jié)點(diǎn)為止。

我們使用一個(gè)patchKeyedChildren函數(shù)去實(shí)現(xiàn)上述過程:
function patchKeyedChildren(n1, n2, container) {
const oldChildren = n1.children
const newChildren = n2.children
//處理前置節(jié)點(diǎn)
let j = 0
let oldVNode = oldChildren[j]
let newVNode = newChildren[j]
while (oldVNode.key === newVNode.key) {
patch(oldVNode, newVNode, container)
j++
oldVNode = oldChildren[j]
newVNode = newChildren[j]
}
//處理后置節(jié)點(diǎn)
//將新舊節(jié)點(diǎn)的索引指向最后一個(gè)子節(jié)點(diǎn)
let oldEnd = oldChildren.length - 1
let newEnd = newChildren.length - 1
oldVNode = oldChildren[oldEnd]
newVNode = newChildren[newEnd]
while (oldVNode.key === newVNode.key) {
patch(oldVNode, newVNode, container)
oldEnd--
newEnd--
oldVNode = oldChildren[oldEnd]
newVNode = newChildren[newEnd]
}
}
2、預(yù)處理之后的兩種情況:需要?jiǎng)h除節(jié)點(diǎn)、需要新增節(jié)點(diǎn)
如何判斷存在需要?jiǎng)h除或者新增的節(jié)點(diǎn)? 在預(yù)處理之后我們可以獲得的信息有:
- 處理前置節(jié)點(diǎn)的時(shí)候獲得的索引
j - 處理后置節(jié)點(diǎn)得到的兩個(gè)索引
newEnd、oldEnd利用以上索引可以做出判斷:
需要新增節(jié)點(diǎn)的情況:oldEnd < j 以及 newEnd >= j:

需要?jiǎng)h除節(jié)點(diǎn)的情況:oldEnd >= j 以及 newEnd < j:

在前文:
Vuejs 數(shù)據(jù)是如何渲染的?渲染器的簡(jiǎn)單實(shí)現(xiàn)
Vue 的渲染器是如何對(duì)節(jié)點(diǎn)進(jìn)行掛載和更新的中實(shí)現(xiàn)的patch和mountElement方法并不能指定位置去掛載節(jié)點(diǎn),為了能夠處理指定節(jié)點(diǎn)位置插入節(jié)點(diǎn),我們需要為其增加一個(gè)參數(shù)anchor,傳入錨點(diǎn)元素。
function patch(n1, n2, container, anchor) {
//...省略代碼
if (typeof type === 'string') {
if (!n1) {
//在此處傳入錨點(diǎn)以支持新節(jié)點(diǎn)按位置插入
mountElement(n2, container, anchor)
} else {
patchElement(n1, n2)
}
} else if (type === Text) {
//...省略代碼
}
function mountElement(vnode, container, anchor) {
//省略代碼
//給insert方法傳遞錨點(diǎn)元素
insert(el, container, anchor)
}
const renderer = createRenderer({
//...省略代碼
insert(el, parent, anchor = null) {
//根據(jù)錨點(diǎn)元素插入節(jié)點(diǎn)
parent.insertBefore(el, anchor)
}
})
接下來我們需要完善patchKeyedChildren去處理上述兩種情況:
需要新增節(jié)點(diǎn)時(shí):
function patchKeyedChildren(n1, n2, container) {
const oldChildren = n1.children
const newChildren = n2.children
//需要插入新節(jié)點(diǎn)
if (j > oldEnd && j <= newEnd){
//取得錨點(diǎn)索引
const anchorIndex = newEnd + 1
//取得錨點(diǎn)元素
const anchor = anchorIndex < newChildren.length ? newChildren[anchorIndex].el : null
//調(diào)用patch掛載新節(jié)點(diǎn)
while (j <= newEnd) {
patch(null, newChildren[j++], container, anchor)
}
}
}
代碼如上,我們首先使用newEnd+1獲取錨點(diǎn)索引,并且使用newChildren[anchorIndex].el去獲取到錨點(diǎn)元素,其中還做了一個(gè)判斷如果newEnd是尾部節(jié)點(diǎn)那不需要提供錨點(diǎn)元素直接處理即可。
需要?jiǎng)h除節(jié)點(diǎn)時(shí):
function patchKeyedChildren(n1, n2, container) {
const oldChildren = n1.children
const newChildren = n2.children
//需要插入新節(jié)點(diǎn)
if (j > oldEnd && j <= newEnd){
//...省略新增節(jié)點(diǎn)邏輯
}else if (j > newEnd && j <= oldEnd) {
//卸載節(jié)點(diǎn)
while (j <= oldEnd) {
unmount(oldChildren[j++])
}
}
}
如上所示,當(dāng)j<=oldEnd時(shí)循環(huán)使用umount卸載對(duì)應(yīng)的節(jié)點(diǎn)即可。
在實(shí)際過程中,很少會(huì)有像上述簡(jiǎn)單的預(yù)處理即可完成大部分工作的情況,這個(gè)時(shí)候就需要進(jìn)行進(jìn)一步的判斷: 比如以下情況:

在經(jīng)過預(yù)處理之后,只有首尾兩個(gè)節(jié)點(diǎn)被正確更新了,仍然會(huì)有多數(shù)節(jié)點(diǎn)沒有被更新。
預(yù)處理之后后續(xù)需要做的是:
- 判斷節(jié)點(diǎn)是否需要移動(dòng),移動(dòng)節(jié)點(diǎn);
- 如果有需要添加或者移除的節(jié)點(diǎn)進(jìn)行處理;
3.判斷節(jié)點(diǎn)是否需要移動(dòng):
1.構(gòu)建source數(shù)組
source數(shù)組需要去存儲(chǔ)新的子節(jié)點(diǎn)對(duì)應(yīng)的舊子節(jié)點(diǎn)的位置索引,然后去計(jì)算一個(gè)最長(zhǎng)遞增子序列,通過最長(zhǎng)遞增子序列去完成DOM的移動(dòng)操作
初始化source數(shù)組:
function patchKeyedChildren(n1, n2, container) {
const oldChildren = n1.children
const newChildren = n2.children
//需要插入新節(jié)點(diǎn)
if (j > oldEnd && j <= newEnd){
//...省略新增節(jié)點(diǎn)邏輯
}else if (j > newEnd && j <= oldEnd) {
//卸載節(jié)點(diǎn)
while (j <= oldEnd) {
unmount(oldChildren[j++])
}
//預(yù)處理完畢后
} else{
//初始化source數(shù)組
const count = newEnd - j + 1
const source = new Array(count)
source.fill(-1)
}
}
source數(shù)組的長(zhǎng)度等于預(yù)處理之后剩余節(jié)點(diǎn)的長(zhǎng)度也就是newEnd - j + 1,我們使用fill將數(shù)組中的元素填充為-1初始化其中的值

填充source數(shù)組: 使用新子節(jié)點(diǎn)在舊子節(jié)點(diǎn)中的索引去填充source數(shù)組

如上key為p-3的新子節(jié)點(diǎn)在舊子節(jié)點(diǎn)中的索引為2,所以source數(shù)組的第一項(xiàng)需要被填充為2,key為p-4的新子節(jié)點(diǎn)在舊子節(jié)點(diǎn)為3,所以source數(shù)組的第二項(xiàng)的值為3,以此類推。 在這個(gè)過程中需要嵌套兩個(gè)for循環(huán)去遍歷新舊子節(jié)類似下面的過程:
for (let i = oldStart; i <= oldEnd; i++) {
const oldVNode = oldChildren[i]
// 遍歷新的一組子節(jié)點(diǎn)
for (let k = newStart; k <= newEnd; k++) {
const newVNode = newChildren[k]
// 找到擁有相同 key 值的可復(fù)用節(jié)點(diǎn)
if (oldVNode.key === newVNode.key) {
// 調(diào)用 patch 進(jìn)行更新
patch(oldVNode, newVNode, container)
// 最后填充 source 數(shù)組
source[k - newStart] = i
}
}
}
以上做法時(shí)間復(fù)雜度為O(n^2),在子節(jié)點(diǎn)數(shù)量增加時(shí)會(huì)存在性能問題。 優(yōu)化的辦法是先遍歷新的一組子節(jié)點(diǎn),根據(jù)子節(jié)點(diǎn)的位置和key生成一張索引表,然后再遍歷舊的一組子節(jié)點(diǎn),利用節(jié)點(diǎn)的key在索引表中找到對(duì)應(yīng)的新子節(jié)點(diǎn)的位置,以此填充source數(shù)組。

const oldStart = j
const newStart = j
const keyIndex = {}
for(let i = newStart; i <= newEnd; i++) {
keyIndex[newChildren[i].key] = i
}
for(let i = oldStart; i <= oldEnd; i++) {
oldVNode = oldChildren[i]
const k = keyIndex[oldVNode.key]
if (typeof k !== 'undefined') {
newVNode = newChildren[k]
patch(oldVNode, newVNode, container)
source[k - newStart] = i
} else {
unmount(oldVNode)
}
}
優(yōu)化后的代碼如上所示:
首先將預(yù)處理之后的j值作為遍歷新舊節(jié)點(diǎn)開始時(shí)的索引,定義一個(gè)對(duì)象keyIndex作為索引表,遍歷預(yù)處理之后剩余的一組新子節(jié)點(diǎn),將新子節(jié)點(diǎn)newChildren[i]的key值與其位置索引放入索引表中。 遍歷舊子節(jié)點(diǎn),在遍歷時(shí),我們可以通過當(dāng)前節(jié)點(diǎn)的key去keyIndex索引表中獲取從而拿到當(dāng)前遍歷的舊子節(jié)點(diǎn)的oldChildren[i]對(duì)應(yīng)的新節(jié)點(diǎn)的位置keyIndex[oldVNode.key],如果位置存在,說明節(jié)點(diǎn)可復(fù)用,使用patch打補(bǔ)丁,并且使用當(dāng)前舊節(jié)點(diǎn)的索引i對(duì)source數(shù)組進(jìn)行填充。
2.標(biāo)識(shí)是否需要移動(dòng)節(jié)點(diǎn)
需要添加標(biāo)識(shí)有:
- 是否需要移動(dòng)
moved: 用于標(biāo)識(shí)是否有需要移動(dòng)的節(jié)點(diǎn), - 當(dāng)前新子節(jié)點(diǎn)的位置
pos: 用于記錄遍歷舊子節(jié)點(diǎn)中遇到的最大的索引值k,如果此次遍歷的k值大于上一次的,說明相對(duì)位置正確無需移動(dòng), - 已經(jīng)更新過的節(jié)點(diǎn)數(shù)量
patched:當(dāng)patched大于source數(shù)組的長(zhǎng)度即newEnd - j + 1時(shí)說明所有可復(fù)用節(jié)點(diǎn)已經(jīng)處理完畢,還有一些舊子節(jié)點(diǎn)需要執(zhí)行卸載操作, 代碼如下,我們?cè)诿恳淮胃鹿?jié)點(diǎn)內(nèi)容后遞增patched++記錄處理數(shù)量,并對(duì)moved和pos的值進(jìn)行處理。
const count = newEnd - j + 1 // 新的一組子節(jié)點(diǎn)中剩余未處理節(jié)點(diǎn)的數(shù)量
const source = new Array(count)
source.fill(-1)
const oldStart = j
const newStart = j
let moved = false
let pos = 0
const keyIndex = {}
for(let i = newStart; i <= newEnd; i++) {
keyIndex[newChildren[i].key] = i
}
let patched = 0
for(let i = oldStart; i <= oldEnd; i++) {
oldVNode = oldChildren[i]
if (patched < count) {
const k = keyIndex[oldVNode.key]
if (typeof k !== 'undefined') {
newVNode = newChildren[k]
patch(oldVNode, newVNode, container)
patched++
source[k - newStart] = i
// 判斷是否需要移動(dòng)
if (k < pos) {
moved = true
} else {
pos = k
}
} else {
// 沒找到
unmount(oldVNode)
}
} else {
unmount(oldVNode)
}
}
4.處理節(jié)點(diǎn)的移動(dòng):
先前我們使用moved去標(biāo)記了是否有至少一個(gè)子節(jié)點(diǎn)需要移動(dòng),當(dāng)moved為true時(shí),我們需要配合source數(shù)組中的最長(zhǎng)遞增子序列去移動(dòng)節(jié)點(diǎn),否則直接不用再去使用diff。
1.最長(zhǎng)遞增子序列:
什么是最長(zhǎng)遞增子序列 遞增子序列就是在一個(gè)序列中,從左到右依次找出更大的值所構(gòu)成的序列,在一個(gè)序列中可能存在多個(gè)遞增子序列,最長(zhǎng)遞增子序列就是其中長(zhǎng)度最長(zhǎng)的那個(gè)。 例如 在上面的例子中我們得到的source數(shù)組為[2, 3, 1, -1],則其最長(zhǎng)遞增子序列為[2,3],我們通過處理得到了對(duì)應(yīng)的舊子節(jié)點(diǎn)的索引[0, 1],即最長(zhǎng)遞增子序列對(duì)應(yīng)的新子節(jié)點(diǎn)的索引。

如上最長(zhǎng)遞增子序列對(duì)應(yīng)的舊節(jié)點(diǎn)為key為p-3、p-4,對(duì)應(yīng)在新子節(jié)點(diǎn)的位置為0,1。
最長(zhǎng)遞增子序列的意義:通過最長(zhǎng)遞增子序列得到的索引可以提示我們哪些元素的相對(duì)位置,在子節(jié)點(diǎn)更新后并未發(fā)生變化,我們可以保留這些節(jié)點(diǎn)的相對(duì)位置,然后去處理和移動(dòng)其他位置。如上p-3和p-4的相對(duì)位置在更新之后并未發(fā)生變化,即新節(jié)點(diǎn)中的索引為0和1的元素不需要移動(dòng)。這里我們省略求最長(zhǎng)遞增子序列的方法,直接將其當(dāng)作函數(shù)lis處理source數(shù)組的結(jié)果
const seq = lis(source)
2.根據(jù)最長(zhǎng)遞增子序列移動(dòng)節(jié)點(diǎn):
創(chuàng)建兩個(gè)索引輔助移動(dòng):
- 索引 i 指向新的一組子節(jié)點(diǎn)中的最后一個(gè)節(jié)點(diǎn)。
- 索引 s 指向最長(zhǎng)遞增子序列中的最后一個(gè)元素。

我們需要去判斷以下的情況:
source[i] === -1: 節(jié)點(diǎn)不存在,需要掛載新節(jié)點(diǎn)i!==seq[s]:節(jié)點(diǎn)需要移動(dòng),i===seq[s]:節(jié)點(diǎn)無需移動(dòng),將s遞減并再次進(jìn)行比較
完善patchKeyedChildren去處理這幾種情況:
function patchKeyedChildren(n1, n2, container) {
//省略預(yù)處理和構(gòu)造source數(shù)組代碼
if (moved) {
const seq = lis(source)
// s 指向最長(zhǎng)遞增子序列的最后一個(gè)值
let s = seq.length - 1
let i = count - 1
for (i; i >= 0; i--) {
if (source[i] === -1) {
// 說明索引為 i 的節(jié)點(diǎn)是全新的節(jié)點(diǎn),應(yīng)該將其掛載
} else if (i !== seq[j]) {
// 說明該節(jié)點(diǎn)需要移動(dòng)
} else {
// 當(dāng) i === seq[j] 時(shí),說明該位置的節(jié)點(diǎn)不需要移動(dòng)
// 并讓 s 指向下一個(gè)位置
s--
}
}
}
}
}
節(jié)點(diǎn)不存在情況具體處理
if (source[i] === -1) {
// 該節(jié)點(diǎn)在新的一組子節(jié)點(diǎn)中的真實(shí)位置索引
const pos = i + newStart
const newVNode = newChildren[pos]
// 該節(jié)點(diǎn)下一個(gè)節(jié)點(diǎn)的位置索引
const nextPos = pos + 1
// 錨點(diǎn)
const anchor = nextPos < newChildren.length
? newChildren[nextPos].el
: null
patch(null, newVNode, container, anchor)
}
代碼如上所示:當(dāng)新子節(jié)點(diǎn)是新節(jié)點(diǎn)時(shí)直接獲取,該節(jié)點(diǎn)的位置,即索引,并且加一獲得錨點(diǎn)用于掛載元素,如果元素本身就是最后一個(gè)元素 nextPos < newChildren.length,則無需錨點(diǎn)。 此時(shí)p-7處理完成,繼續(xù)向上處理p-2

節(jié)點(diǎn)需要移動(dòng)的情況
if (i !== seq[s]) {
// 該節(jié)點(diǎn)在新的一組子節(jié)點(diǎn)中的真實(shí)位置索引
const pos = i + newStart
const newVNode = newChildren[pos]
// 該節(jié)點(diǎn)下一個(gè)節(jié)點(diǎn)的位置索引
const nextPos = pos + 1
// 錨點(diǎn)
const anchor = nextPos < newChildren.length
? newChildren[nextPos].el
: null
patch(null, newVNode, container, anchor)
}
邏輯和節(jié)點(diǎn)不存在的情況類似,只是移動(dòng)節(jié)點(diǎn)通過insert函數(shù)去完成。此時(shí)處理的結(jié)果如下

節(jié)點(diǎn)不需要移動(dòng)的情況 對(duì)于p-3和p-4來說,source[i] !== -1,并且i === seq[s],即節(jié)點(diǎn)無需移動(dòng)只需更新s的值即可
s--
依此類推直到循環(huán)結(jié)束,子節(jié)點(diǎn)全部更新完畢,該過程完整代碼如下:
if (moved) {
const seq = lis(source)
// s 指向最長(zhǎng)遞增子序列的最后一個(gè)值
let s = seq.length - 1
let i = count - 1
for (i; i >= 0; i--) {
if (source[i] === -1) {
// 說明索引為 i 的節(jié)點(diǎn)是全新的節(jié)點(diǎn),應(yīng)該將其掛載
// 該節(jié)點(diǎn)在新 children 中的真實(shí)位置索引
const pos = i + newStart
const newVNode = newChildren[pos]
// 該節(jié)點(diǎn)下一個(gè)節(jié)點(diǎn)的位置索引
const nextPos = pos + 1
// 錨點(diǎn)
const anchor = nextPos < newChildren.length
? newChildren[nextPos].el
: null
// 掛載
patch(null, newVNode, container, anchor)
} else if (i !== seq[j]) {
// 說明該節(jié)點(diǎn)需要移動(dòng)
// 該節(jié)點(diǎn)在新的一組子節(jié)點(diǎn)中的真實(shí)位置索引
const pos = i + newStart
const newVNode = newChildren[pos]
// 該節(jié)點(diǎn)下一個(gè)節(jié)點(diǎn)的位置索引
const nextPos = pos + 1
// 錨點(diǎn)
const anchor = nextPos < newChildren.length
? newChildren[nextPos].el
: null
// 移動(dòng)
insert(newVNode.el, container, anchor)
} else {
// 當(dāng) i === seq[j] 時(shí),說明該位置的節(jié)點(diǎn)不需要移動(dòng)
// 并讓 s 指向下一個(gè)位置
s--
}
}
}
}
總結(jié):
使用
diff算法的原因:- 傳統(tǒng)的 DOM 更新方法會(huì)在有新舊子節(jié)點(diǎn)時(shí)卸載舊節(jié)點(diǎn)并掛載新節(jié)點(diǎn),這種方法沒有考慮到節(jié)點(diǎn)的復(fù)用可能性。
diff算法通過比較新舊節(jié)點(diǎn)的差異來復(fù)用節(jié)點(diǎn),從而優(yōu)化性能。
- 傳統(tǒng)的 DOM 更新方法會(huì)在有新舊子節(jié)點(diǎn)時(shí)卸載舊節(jié)點(diǎn)并掛載新節(jié)點(diǎn),這種方法沒有考慮到節(jié)點(diǎn)的復(fù)用可能性。
節(jié)點(diǎn)復(fù)用依據(jù):key:
- 節(jié)點(diǎn)復(fù)用是通過比較節(jié)點(diǎn)的
key和類型來實(shí)現(xiàn)的。相同的key和類型表明兩個(gè)節(jié)點(diǎn)可以被視為同一個(gè),從而復(fù)用以減少 DOM 操作。
- 節(jié)點(diǎn)復(fù)用是通過比較節(jié)點(diǎn)的
Vue 3 diff算法的過程:
- 預(yù)處理階段:處理首尾節(jié)點(diǎn),找出新舊兩種子節(jié)點(diǎn)中首尾可復(fù)用的節(jié)點(diǎn)并更新。
- 處理理想情況下新增和刪除節(jié)點(diǎn):若通過預(yù)處理有一組節(jié)點(diǎn)已經(jīng)更新完畢,證明新的一組子節(jié)點(diǎn)只需新增或刪除部分節(jié)點(diǎn)即可完成更新。
- 構(gòu)造source數(shù)組:通過遍歷新舊兩組子節(jié)點(diǎn),構(gòu)造一個(gè)source數(shù)組,去存儲(chǔ)新的子節(jié)點(diǎn)對(duì)應(yīng)的舊子節(jié)點(diǎn)的位置索引,并在此過程中判斷是否需要使用diff算法處理移動(dòng)。
- 節(jié)點(diǎn)位置移動(dòng):根據(jù)最長(zhǎng)遞增子序列判斷具體的某個(gè)節(jié)點(diǎn)是否需要新增或者移動(dòng),在需要時(shí)移動(dòng)節(jié)點(diǎn)以匹配新的子節(jié)點(diǎn)順序。
diff算法帶來的效率提升:
- 算法避免了全量的 DOM 更新,通過巧妙的方法判斷哪些節(jié)點(diǎn)需要更新、移動(dòng)或重新掛載,從而降低了全量更新的成本和時(shí)間。
以上就是Vue3快速diff算法的處理過程的詳細(xì)內(nèi)容,更多關(guān)于Vue3快速diff算法的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue3中關(guān)于路由hash與History的設(shè)置
這篇文章主要介紹了vue3中關(guān)于路由hash與History的設(shè)置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08
解決VUEX的mapState/...mapState等取值問題
這篇文章主要介紹了解決VUEX的mapState/...mapState等取值問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-07-07
vue利用全局導(dǎo)航守衛(wèi)作登錄后跳轉(zhuǎn)到未登錄前指定頁面的實(shí)例代碼
這篇文章主要介紹了vue利用全局導(dǎo)航守衛(wèi)作登錄后跳轉(zhuǎn)到未登錄前指定頁面,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05
vue3+ts+vite2項(xiàng)目實(shí)戰(zhàn)踩坑記錄
最近嘗試上手Vue3+TS+Vite,對(duì)比起Vue2有些不適應(yīng),但還是真香,下面這篇文章主要給大家介紹了關(guān)于vue3+ts+vite2項(xiàng)目的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-09-09
Vue-Router如何動(dòng)態(tài)更改當(dāng)前頁url query
這篇文章主要介紹了Vue-Router如何動(dòng)態(tài)更改當(dāng)前頁url query問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08
vue跳轉(zhuǎn)同一個(gè)路由參數(shù)不同的問題
這篇文章主要介紹了vue跳轉(zhuǎn)同一個(gè)路由參數(shù)不同的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08
npm install報(bào)錯(cuò)缺少python問題及解決
這篇文章主要介紹了npm install報(bào)錯(cuò)缺少python問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06

