vue頁面更新patch的實現(xiàn)示例
patch的流程
組件頁面渲染時,將render返回的新vnode(新節(jié)點)和組件實例保存的vnode(舊節(jié)點)作為參數(shù),調(diào)用patch方法,更新DOM。
判斷兩個節(jié)點是否相同
處理過程中,需要判斷節(jié)點是否相同。相同節(jié)點需要滿足以下條件:
- key相同
- 標簽類型相同
- 注釋節(jié)點標識相同,都是注釋節(jié)點,或者都不是注釋節(jié)點
- data的值狀態(tài)相同,或者都有值,或者都沒值
function sameVnode (a, b) {// 判斷兩個VNode節(jié)點是否是同一個節(jié)點
return (
a.key === b.key && // key相同
(
a.tag === b.tag && // tag相同
a.isComment === b.isComment && // 注釋節(jié)點標識相同
isDef(a.data) === isDef(b.data) && // data值狀態(tài)相同
sameInputType(a, b) // input的type相同
)
)
}
patch方法
patch判斷流程如下:
a) 如果新節(jié)點為空,此時舊節(jié)點存在(組件銷毀時),調(diào)用舊節(jié)點destroy生命周期函數(shù)
b) 如果舊節(jié)點為空,根據(jù)新節(jié)點創(chuàng)建DOM
c) 其他(如果新舊節(jié)點都存在)
- a) 舊節(jié)點不是DOM(組件節(jié)點),且新舊節(jié)點相同
- 執(zhí)行patchVnode
- b) 舊節(jié)點是DOM元素或者兩個節(jié)點不相同
- 創(chuàng)建新節(jié)點DOM,銷毀舊節(jié)點以及DOM。
function patch (oldVnode, vnode, hydrating, removeOnly) {
if (isUndef(vnode)) {
if (isDef(oldVnode)) { invokeDestroyHook(oldVnode); }
return
}
...
if (isUndef(oldVnode)) {
isInitialPatch = true;// 組件初始加載
createElm(vnode, insertedVnodeQueue);
} else {
var isRealElement = isDef(oldVnode.nodeType);
if (!isRealElement && sameVnode(oldVnode, vnode)) {
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly);
} else {
...
var oldElm = oldVnode.elm;
var parentElm = nodeOps.parentNode(oldElm);// 獲取父元素
// create new node
createElm(
vnode,
insertedVnodeQueue,
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)// 獲取緊跟的弟弟元素
);
if (isDef(parentElm)) {
removeVnodes(parentElm, [oldVnode], 0, 0);// 銷毀舊節(jié)點以及DOM元素
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode);
}
}
}
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
return vnode.elm
}
}
patchVnode方法
當兩個節(jié)點相同時,執(zhí)行patchVnode方法。在處理各種情況之前,會將舊節(jié)點elm屬性值賦值給新節(jié)點的elm屬性,保持elm保持一致。
具體流程如下:
a)如果新舊節(jié)點完全相同(引用相同 oldVnode === vnode)
- 直接返回不處理
b) 如果新節(jié)點不是文本節(jié)點
- a)都存在子節(jié)點,新舊節(jié)點的子節(jié)點數(shù)組引用不同(oldCh !== ch)
- updateChildren
- b)新節(jié)點有子節(jié)點,舊節(jié)點沒有
- 1)查重子節(jié)點(key)
- 2)如果舊節(jié)點是文本節(jié)點,先清空文本
- 3)創(chuàng)建子節(jié)點DOM元素
- c)舊節(jié)點有子節(jié)點,新節(jié)點沒有
- 移除子節(jié)點以及DOM
- d)舊節(jié)點是文本節(jié)點
- 清除文本
- c)如果新節(jié)點是文本節(jié)點,并且和舊節(jié)點文本不相同
- 則直接替換文本內(nèi)容。
- d)其他(新節(jié)點是文本節(jié)點,并且和舊節(jié)點相同)
- 不處理
function patchVnode (
oldVnode,
vnode,
insertedVnodeQueue,
ownerArray,
index,
removeOnly
) {
if (oldVnode === vnode) {
return
}
...
if (isUndef(vnode.text)) {
if (isDef(oldCh) && isDef(ch)) {
if (oldCh !== ch) { updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly); }
} 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);
} else if (isDef(oldCh)) {
removeVnodes(elm, oldCh, 0, oldCh.length - 1);
} else if (isDef(oldVnode.text)) {
nodeOps.setTextContent(elm, '');
}
} else if (oldVnode.text !== vnode.text) {
nodeOps.setTextContent(elm, vnode.text);
}
...
}
updateChildren方法
updateChildren方法處理相同新舊節(jié)點的子節(jié)點。方法定義了以下變量(updateChildren的節(jié)點都表示的是子節(jié)點):
var oldStartIdx = 0;// 表示當前正在處理的舊起始節(jié)點序號
var newStartIdx = 0;// 表示當前正在處理的新起始節(jié)點序號
var oldEndIdx = oldCh.length - 1;// 表示當前正在處理的舊結尾節(jié)點序號
var oldStartVnode = oldCh[0];// 表示當前正在處理的舊起始節(jié)點
var oldEndVnode = oldCh[oldEndIdx];// 表示當前正在處理的舊結尾節(jié)點
var newEndIdx = newCh.length - 1;// 表示當前正在處理的新結尾節(jié)點序號
var newStartVnode = newCh[0];// 表示當前正在處理的新起始節(jié)點
var newEndVnode = newCh[newEndIdx];// 表示當前正在處理的新結尾節(jié)點
var oldKeyToIdx, // 尚未處理的舊節(jié)點key值映射
idxInOld, // 與新節(jié)點key值相同的舊節(jié)點序號
vnodeToMove, // 與新節(jié)點key值相同的舊節(jié)點
refElm;// 指向當前正在處理的新結尾節(jié)點的后一個節(jié)點(已處理)的DOM元素
根據(jù)新舊節(jié)點的對比結果,更新DOM元素,此過程并不改變新舊節(jié)點的排序。序號指向正在處理的節(jié)點,分別是新舊節(jié)點的起始和結尾節(jié)點。對比過程以新起始節(jié)點為主導,對比方向是由兩側向中間。優(yōu)先比對新舊節(jié)點的起始節(jié)點和結尾節(jié)點,再查找與新起始節(jié)點相同的且未處理的舊節(jié)點。當舊節(jié)點全部處理完(舊起始和結尾序號重疊),此時新節(jié)點可能未處理完,就添加新節(jié)點DOM元素。當新節(jié)點全部處理完(新起始和結尾序號重疊),可能存在舊節(jié)點,就刪除舊節(jié)點DOM元素。
具體流程如下:
新舊子節(jié)點的起始序號不大于結尾序號時,執(zhí)行以下流程:
a)如果舊子節(jié)點兩側存在undefined節(jié)點
- 舊起始節(jié)點
undefined,oldStartVnode = oldCh[++oldStartIdx] - 舊結尾節(jié)點
undefined,oldEndVnode = oldCh[--oldEndIdx]
b)新舊子節(jié)點的起始節(jié)點相同(前后比較)
patchVNode更新DOM內(nèi)容oldStartVnode = oldCh[++oldStartIdx]newStartVnode = newCh[++newStartIdx]
c)新舊子節(jié)點的結尾節(jié)點相同(前后比較)
patchVNode更新DOM內(nèi)容oldEndVnode = oldCh[--oldEndIdx]newEndVnode = newCh[--newEndIdx]
d)舊起始節(jié)點和新結尾節(jié)點相同(前后比較)
patchVNode更新DOM內(nèi)容- 將舊起始節(jié)點DOM添加到舊結尾節(jié)點DOM前面
oldStartVnode = oldCh[++oldStartIdx]newEndVnode = newCh[--newEndIdx]
e)舊結尾節(jié)點和新起始節(jié)點相同(前后比較)
patchVNode更新DOM內(nèi)容- 將舊結尾節(jié)點DOM添加到舊起始節(jié)點DOM前面
oldEndVnode = oldCh[--oldEndIdx]newStartVnode = newCh[++newStartIdx]
f)其他(緩存尚未處理的舊節(jié)點key值,依此判斷舊節(jié)點中是否存在和新起始節(jié)點相同的節(jié)點)
- a)尚未處理的舊節(jié)點中不存在與新起始節(jié)點相同的節(jié)點
- 創(chuàng)建新節(jié)點DOM并添加到舊起始節(jié)點DOM的前面
newStartVnode = newCh[++newStartIdx]
- b)舊節(jié)點中存在與新起始節(jié)點key相同的節(jié)點
- a)舊節(jié)點中存在與新起始節(jié)點相同的節(jié)點
patchVode- 將相同的舊節(jié)點DOM添加到舊起始節(jié)點DOM前面
- 將相同的舊節(jié)點置為undefined
oldCh[idxInOld] = undefined newStartVnode = newCh[++newStartIdx]
- b)key相同,但標簽類型不同的節(jié)點
- 創(chuàng)建新節(jié)點DOM并添加到舊起始節(jié)點DOM的前面
newStartVnode = newCh[++newStartIdx]
- a)舊節(jié)點中存在與新起始節(jié)點相同的節(jié)點
循環(huán)結束
a)如果舊節(jié)點遍歷完(oldStartIdx > oldEndIdx)
- 把剩余未處理新節(jié)點DOM添加到上一個新結尾節(jié)點DOM前面(從新起始節(jié)點到新結尾節(jié)點,都未處理過)
b)如果新節(jié)點遍歷完(newStartIdx > newEndIdx)
- 移除舊起始和結尾節(jié)點以及他們之間的節(jié)點的DOM(從舊起始節(jié)點到舊結尾節(jié)點,可能存在處理過的節(jié)點,但處理過已被置為undefined)
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
var oldStartIdx = 0;// 表示當前正在處理的舊起始節(jié)點序號
var newStartIdx = 0;// 表示當前正在處理的新起始節(jié)點序號
var oldEndIdx = oldCh.length - 1;// 表示當前正在處理的舊結尾節(jié)點序號
var oldStartVnode = oldCh[0];// 表示當前正在處理的舊起始節(jié)點
var oldEndVnode = oldCh[oldEndIdx];// 表示當前正在處理的舊結尾節(jié)點
var newEndIdx = newCh.length - 1;// 表示當前正在處理的新結尾節(jié)點序號
var newStartVnode = newCh[0];// 表示當前正在處理的新起始節(jié)點
var newEndVnode = newCh[newEndIdx];// 表示當前正在處理的新結尾節(jié)點
var oldKeyToIdx, idxInOld, vnodeToMove, refElm;
...
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx];
} else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx);
oldStartVnode = oldCh[++oldStartIdx];
newStartVnode = newCh[++newStartIdx];
} else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx);
oldEndVnode = oldCh[--oldEndIdx];
newEndVnode = newCh[--newEndIdx];
} 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];
} 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 {
if (isUndef(oldKeyToIdx)) { oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); }// 緩存尚未處理的舊節(jié)點key值
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];
}
}
if (oldStartIdx > oldEndIdx) {
refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm;
addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue);
} else if (newStartIdx > newEndIdx) {
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
}
}
updateChildren的示例:
1.左邊表示新舊節(jié)點,節(jié)點下面標識起始和結尾節(jié)點(即正在處理的節(jié)點)。右邊表示當前的DOM。

2.新節(jié)點的起始和結尾節(jié)點與舊節(jié)點的起始和結尾節(jié)點互不相同,并且在舊節(jié)點中未找到與新起始節(jié)點(新節(jié)點f)相同的節(jié)點。
所以創(chuàng)建節(jié)點f的DOM并添加到舊起始節(jié)點(舊節(jié)點a)DOM的前面,然后新起始節(jié)點序號加1,表示新節(jié)點f已處理,當前正在處理新起始節(jié)點c。

3.新節(jié)點的起始和結尾節(jié)點與舊節(jié)點的起始和結尾節(jié)點互不相同,但在舊節(jié)點中找到與新起始節(jié)點(節(jié)點c)相同的節(jié)點。
所以將舊節(jié)點c的DOM添加到舊起始節(jié)點(舊節(jié)點a)DOM的前面,舊節(jié)點c置空,然后新起始節(jié)點序號加1,表示新節(jié)點c已處理,當前正在處理新起始節(jié)點e。

4.新起始節(jié)點(新節(jié)點e)和舊結尾節(jié)點(舊節(jié)點e)相同。更新舊節(jié)點e的DOM內(nèi)容,并將舊節(jié)點e的DOM移動到舊起始節(jié)點(舊節(jié)點a)DOM的前面,舊結尾節(jié)點序號減1,新起始節(jié)點加1,表示新舊節(jié)點e已處理,當前正在處理的是新起始節(jié)點g和舊結尾節(jié)點d。

5.新結尾節(jié)點(新節(jié)點d)和舊結尾節(jié)點(舊節(jié)點d)相同。僅更新舊節(jié)點d的DOM內(nèi)容。新結尾節(jié)點序號減1,舊結尾節(jié)點序號減1,表示新舊節(jié)點d已處理,當前正在處理的是新結尾節(jié)點g和舊結尾節(jié)點c。由于舊節(jié)點c為空,則舊結尾節(jié)點為b。

6.新節(jié)點的起始和結尾節(jié)點與舊節(jié)點的起始和結尾節(jié)點互不相同,并且在舊節(jié)點中未找到與新起始節(jié)點(新節(jié)點g)相同的節(jié)點。
所以創(chuàng)建節(jié)點g的DOM并添加到舊起始節(jié)點(舊節(jié)點a)DOM的前面,然后新起始節(jié)點序號加1,表示新節(jié)點g已處理,當前正在處理新起始節(jié)點d。

7.由于新起始和結尾節(jié)點序號重疊,新節(jié)點已經(jīng)處理完畢,存在尚未處理的舊節(jié)點,則移除未處理的舊節(jié)點DOM。

8.結束,最終的DOM。

到此這篇關于vue頁面更新patch的實現(xiàn)示例的文章就介紹到這了,更多相關vue 更新patch內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
VUE實現(xiàn)一個Flappy Bird游戲的示例代碼
這篇文章主要介紹了VUE實現(xiàn)一個Flappy Bird的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-04-04
Vue動態(tài)修改網(wǎng)頁標題的方法及遇到問題
Vue下有很多的方式去修改網(wǎng)頁標題,這里總結下解決此問題的幾種方案:,需要的朋友可以參考下2019-06-06

