您好,登錄后才能下訂單哦!
本文小編為大家詳細(xì)介紹“vue2中的VNode和diff算法怎么使用”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“vue2中的VNode和diff算法怎么使用”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來(lái)學(xué)習(xí)新知識(shí)吧。
我們知道,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>
它換成 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,
},
],
};
總的來(lái)說(shuō),VNode
就是一個(gè) JavaScript
對(duì)象。這個(gè)JavaScript
對(duì)象能完整地表示出真實(shí)DOM
。
筆者認(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è)面性能。
下面我們來(lái)看看組件更新所涉及到的diff算法
。
前面我們講依賴(lài)收集的時(shí)候有說(shuō)到,渲染watcher
傳遞給Watcher
的get
方法其實(shí)是updateComponent
方法。
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
所以組件在響應(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)
}
// ...
}
下面我們?cè)賮?lái)看看__patch__
方法
__patch__
patch
方法接收四個(gè)參數(shù),由于初始渲染的時(shí)候oldVnode
為vm.$el
是null
,所以初始渲染是沒(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
}
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)
}
判斷兩個(gè)VNode
節(jié)點(diǎn)是否是同一個(gè)節(jié)點(diǎn),需要同時(shí)滿(mǎn)足以下條件
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è)VNode
的tag、key、isComment
都相同,并且同時(shí)定義或未定義data
的時(shí)候,且如果標(biāo)簽為input則type
必須相同。這時(shí)候這兩個(gè)VNode
則算sameVnode
,可以直接進(jìn)行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)
}
}
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è)updateChildren
是diff算法
的核心,后面我們會(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
算法的核心,下面我們來(lái)重點(diǎn)分析。
這兩張圖代表舊的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)
// 如果滿(mǎn)足 newStartIdx > newEndIdx 條件,說(shuō)明新節(jié)點(diǎn)比對(duì)完了,老節(jié)點(diǎn)還有多,
// 將這些無(wú)用的老節(jié)點(diǎn)通過(guò) removeVnodes 批量刪除即可。
} else if (newStartIdx > newEndIdx) {
removeVnodes(oldCh, oldStartIdx, oldEndIdx)
}
}
vue2
的diff
算法采用的是雙端比較
,所謂雙端比較
就是新列表和舊列表兩個(gè)列表的頭與尾互相對(duì)比,在對(duì)比的過(guò)程中指針會(huì)逐漸向內(nèi)靠攏,直到某一個(gè)列表的節(jié)點(diǎn)全部遍歷過(guò),對(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ì)比
首先是 oldStartVnode
與 newStartVnode
符合 sameVnode
時(shí),說(shuō)明老 VNode 節(jié)點(diǎn)的頭部與新 VNode 節(jié)點(diǎn)的頭部是相同的 VNode 節(jié)點(diǎn),直接進(jìn)行 patchVnode
,同時(shí) oldStartIdx
與 newStartIdx
向后移動(dòng)一位。
其次是 oldEndVnode
與 newEndVnode
符合 sameVnode
,也就是兩個(gè) VNode 的結(jié)尾是相同的 VNode,同樣進(jìn)行 patchVnode
操作并將 oldEndVnode
與 newEndVnode
向前移動(dòng)一位。
接下來(lái)是兩種交叉的情況。
先是 oldStartVnode
與 newEndVnode
符合 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)一位。
同理,oldEndVnode
與 newStartVnode
符合 sameVnode
時(shí),也就是老 VNode 節(jié)點(diǎn)的尾部與新 VNode 節(jié)點(diǎn)的頭部是同一節(jié)點(diǎn)的時(shí)候,將 oldEndVnode.elm
插入到 oldStartVnode.elm
前面。同樣的,oldEndIdx
向前移動(dòng)一位,newStartIdx
向后移動(dòng)一位。
最后是當(dāng)以上情況都不符合的時(shí)候,這種情況怎么處理呢?
那就是查找對(duì)比。
首先通過(guò)createKeyToOldIdx
方法生成oldVnode
的key
與 index
索引對(duì)應(yīng)的一個(gè) map
表。
然后我們根據(jù)newStartVnode.key
,可以快速地從 oldKeyToIdx
(createKeyToOldIdx
的返回值)中獲取相同 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)一位。
如果不符合 sameVnode
,只能創(chuàng)建一個(gè)新節(jié)點(diǎn)插入到 parentElm
的子節(jié)點(diǎn)中,newStartIdx
往后移動(dòng)一位。
最后一步就很容易啦,當(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)插入即可。
同理,如果滿(mǎn)足 newStartIdx > newEndIdx
條件,說(shuō)明新節(jié)點(diǎn)比對(duì)完了,老節(jié)點(diǎn)還有多,將這些無(wú)用的老節(jié)點(diǎn)通過(guò) removeVnodes
批量刪除即可。
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)。
key
是 Vue
中 vnode
的唯一標(biāo)記,通過(guò)這個(gè) key
,diff
操作可以更準(zhǔn)確、更快速。
更準(zhǔn)確:因?yàn)閹?key
就不是就地復(fù)用了,在 sameNode
函數(shù) a.key === b.key
對(duì)比中可以避免就地復(fù)用的情況。所以會(huì)更加準(zhǔn)確。
更快速:利用 key
的唯一性生成 map
對(duì)象來(lái)獲取對(duì)應(yīng)節(jié)點(diǎn),比遍歷方式更快。
當(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ě)是一樣的。
讀到這里,這篇“vue2中的VNode和diff算法怎么使用”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識(shí)點(diǎn)還需要大家自己動(dòng)手實(shí)踐使用過(guò)才能領(lǐng)會(huì),如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(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)容。