寫文章不容易,點個贊呗兄弟
專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于了解工作原理,源碼版助于了解内部詳情,讓我們一起學習吧
研究基于 Vue版本 【2.5.17】
如果你覺得排版難看,請點選 下面連結 或者 拉到 下面關注公衆号也可以吧
Diff - 白話版
下面開始我們的正文
在之前一篇文章 createPatchFunction
function createPatchFunction() {
return function patch(
oldVnode, vnode, parentElm, refElm
) {
// 沒有舊節點,直接生成新節點
if (!oldVnode) {
createElm(vnode, parentElm, refElm);
}
else {
// 且是一樣 Vnode
if (sameVnode(oldVnode, vnode)) {
// 比較存在的根節點
patchVnode(oldVnode, vnode);
}
else {
// 替換存在的元素
var oldElm = oldVnode.elm;
var _parentElm = oldElm.parentNode
// 建立新節點
createElm(vnode, _parentElm, oldElm.nextSibling);
// 銷毀舊節點
if (_parentElm) {
removeVnodes([oldVnode], 0, 0);
}
}
}
return vnode.elm
}
}
這個函數的作用就是
比較 新節點 和 舊節點 有什麼不同,然後完成更新
是以你看到接收一個 oldVnode 和 vnode
處理的流程分為
1、沒有舊節點
2、舊節點 和 新節點 自身一樣(不包括其子節點)
3、舊節點 和 新節點自身不一樣
速度來看下這三個流程了
1 沒有舊節點
沒有舊節點,說明是頁面剛開始初始化的時候,此時,根本不需要比較了
直接全部都是建立,是以隻調用 createElm
2 舊節點 和 新節點 自身一樣
通過 sameVnode 判斷節點是否一樣,這個函數在上篇文章中說過了
舊節點 和 新節點自身一樣時,直接調用 patchVnode 去處理這兩個節點
patchVnode 下面會講到這個函數
在講 patchVnode 之前,我們先思考這個函數的作用是什麼?
當兩個Vnode自身一樣的時候,我們需要做什麼?
首先,自身一樣,我們可以先簡單了解,是 Vnode 的兩個屬性 tag 和 key 一樣
那麼,我們是不知道其子節點是否一樣的,是以肯定需要比較子節點
是以,patchVnode 其中的一個作用,就是比較子節點
3 舊節點 和 新節點 自身不一樣
當兩個節點不一樣的時候,不難了解,直接建立新節點,删除舊節點
patchVnode
在上一個函數 createPatchFunction 中,有出現一個函數 patchVnode
我們思考了這個函數的其中的一個作用是 比較兩個Vnode 的子節點
是不是我們想的呢,可以先來過一下源碼
function patchVnode(oldVnode, vnode) {
if (oldVnode === vnode) return
var elm = vnode.elm = oldVnode.elm;
var oldCh = oldVnode.children;
var ch = vnode.children;
// 更新children
if (!vnode.text) {
// 存在 oldCh 和 ch 時
if (oldCh && ch) {
if (oldCh !== ch)
updateChildren(elm, oldCh, ch);
}
// 存在 newCh 時,oldCh 隻能是不存在,如果存在,就跳到上面的條件了
else if (ch) {
if (oldVnode.text) elm.textContent = '';
for (var i = 0; i <= ch.length - 1; ++i) {
createElm(
ch[i],elm, null
);
}
}
else if (oldCh) {
for (var i = 0; i<= oldCh.length - 1; ++i) {
oldCh[i].parentNode.removeChild(el);
}
}
else if (oldVnode.text) {
elm.textContent = '';
}
}
else if (oldVnode.text !== vnode.text) {
elm.textContent = vnode.text;
}
}
我們現在就來分析這個函數
沒錯,正如我們所想,這個函數的确會去比較處理子節點
總的來說,這個函數的作用是
1、Vnode 是文本節點,則更新文本(文本節點不存在子節點)
2、Vnode 有子節點,則處理比較更新子節點
更進一步的總結就是,這個函數主要做了兩種判斷的處理
1、Vnode 是否是文本節點
2、Vnode 是否有子節點
下面我們來看看這些步驟的詳細分析
1 Vnode是文本節點
當 VNode 存在 text 這個屬性的時候,就證明了 Vnode 是文本節點
我們可以先來看看 文本類型的 Vnode 是什麼樣子
是以當 Vnode 是文本節點的時候,需要做的就是,更新文本
同樣有兩種處理
1、當 新Vnode.text 存在,而且和 舊 VNode.text 不一樣時
直接更新這個 DOM 的 文本内容
elm.textContent = vnode.text;
注:textContent 是 真實DOM 的一個屬性, 儲存的是 dom 的文本,是以直接更新這個屬性
2、新Vnode 的 text 為空,直接把 文本DOM 指派給空
elm.textContent = '';
2 Vnode存在子節點
當 Vnode 存在子節點的時候,因為不知道 新舊節點的子節點是否一樣,是以需要比較,才能完成更新
這裡有三種處理
1、新舊節點 都有子節點,而且不一樣
2、隻有新節點
3、隻有舊節點
後面兩個節點,相信大家都能想通,但是我們還是說一下
1 隻有新節點
隻有新節點,不存在舊節點,那麼沒得比較了,所有節點都是全新的
是以直接全部建立就好了,建立是指建立出所有新DOM,并且添加進父節點的
2 隻有舊節點
隻有舊節點而沒有新節點,說明更新後的頁面,舊節點全部都不見了
那麼要做的,就是把所有的舊節點删除
也就是直接把DOM 删除
3 新舊節點 都有子節點,而且不一樣
咦惹,又出現了一個新函數,那就是 updateChildren
預告一下,這個函數非常的重要,是 Diff 的核心子產品,蘊含着 Diff 的思想
可能會有點繞,但是不用怕,相信在我的探索之下,可以稍微明白些
同樣的,我們先來思考下 updateChildren 的作用
記得條件,當新節點 和 舊節點 都存在,要怎麼去比較才能知道有什麼不一樣呢?
哦沒錯,使用周遊,新子節點和舊子節點一個個比較
如果一樣,就不更新,如果不一樣,就更新
下面就來驗證下我們的想法,來探索一下 updateChildren 的源碼
updateChildren
這個函數非常的長,但是其實不難,就是分了幾種處理流程而已,但是一開始看可能有點懵
或者可以先跳過源碼,看下分析,或者便看分析邊看源碼
function updateChildren(parentElm, oldCh, newCh) {
var oldStartIdx = 0;
var oldEndIdx = oldCh.length - 1;
var oldStartVnode = oldCh[0];
var oldEndVnode = oldCh[oldEndIdx];
var newStartIdx = 0;
var newEndIdx = newCh.length - 1;
var newStartVnode = newCh[0];
var newEndVnode = newCh[newEndIdx];
var oldKeyToIdx, idxInOld, vnodeToMove, refElm;
// 不斷地更新 OldIndex 和 OldVnode ,newIndex 和 newVnode
while (
oldStartIdx <= oldEndIdx &&
newStartIdx <= newEndIdx
) {
if (!oldStartVnode) {
oldStartVnode = oldCh[++oldStartIdx];
}
else if (!oldEndVnode) {
oldEndVnode = oldCh[--oldEndIdx];
}
// 舊頭 和新頭 比較
else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode);
oldStartVnode = oldCh[++oldStartIdx];
newStartVnode = newCh[++newStartIdx];
}
// 舊尾 和新尾 比較
else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode);
oldEndVnode = oldCh[--oldEndIdx];
newEndVnode = newCh[--newEndIdx];
}
// 舊頭 和 新尾 比較
else if (sameVnode(oldStartVnode, newEndVnode)) {
patchVnode(oldStartVnode, newEndVnode);
// oldStartVnode 放到 oldEndVnode 後面,還要找到 oldEndValue 後面的節點
parentElm.insertBefore(
oldStartVnode.elm,
oldEndVnode.elm.nextSibling
);
oldStartVnode = oldCh[++oldStartIdx];
newEndVnode = newCh[--newEndIdx];
}
// 舊尾 和新頭 比較
else if (sameVnode(oldEndVnode, newStartVnode)) {
patchVnode(oldEndVnode, newStartVnode);
// oldEndVnode 放到 oldStartVnode 前面
parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm);
oldEndVnode = oldCh[--oldEndIdx];
newStartVnode = newCh[++newStartIdx];
}
// 單個新子節點 在 舊子節點數組中 查找位置
else {
// oldKeyToIdx 是一個 把 Vnode 的 key 和 index 轉換的 map
if (!oldKeyToIdx) {
oldKeyToIdx = createKeyToOldIdx(
oldCh, oldStartIdx, oldEndIdx
);
}
// 使用 newStartVnode 去 OldMap 中尋找 相同節點,預設key存在
idxInOld = oldKeyToIdx[newStartVnode.key]
// 新孩子中,存在一個新節點,老節點中沒有,需要建立
if (!idxInOld) {
// 把 newStartVnode 插入 oldStartVnode 的前面
createElm(
newStartVnode,
parentElm,
oldStartVnode.elm
);
}
else {
// 找到 oldCh 中 和 newStartVnode 一樣的節點
vnodeToMove = oldCh[idxInOld];
if (sameVnode(vnodeToMove, newStartVnode)) {
patchVnode(vnodeToMove, newStartVnode);
// 删除這個 index
oldCh[idxInOld] = undefined;
// 把 vnodeToMove 移動到 oldStartVnode 前面
parentElm.insertBefore(
vnodeToMove.elm,
oldStartVnode.elm
);
}
// 隻能建立一個新節點插入到 parentElm 的子節點中
else {
// same key but different element. treat as new element
createElm(
newStartVnode,
parentElm,
oldStartVnode.elm
);
}
}
// 這個新子節點更新完畢,更新 newStartIdx,開始比較下一個
newStartVnode = newCh[++newStartIdx];
}
}
// 處理剩下的節點
if (oldStartIdx > oldEndIdx) {
var newEnd = newCh[newEndIdx + 1]
refElm = newEnd ? newEnd.elm :null;
for (; newStartIdx <= newEndIdx; ++newStartIdx) {
createElm(
newCh[newStartIdx], parentElm, refElm
);
}
}
// 說明新節點比對完了,老節點可能還有,需要删除剩餘的老節點
else if (newStartIdx > newEndIdx) {
for (; oldStartIdx<=oldEndIdx; ++oldStartIdx) {
oldCh[oldStartIdx].parentNode.removeChild(el);
}
}
}
首先要明确這個函數處理的是什麼
處理的是 新子節點 和 舊子節點,循環周遊逐個比較
如何 循環周遊?
1、使用 while
2、新舊節點數組都配置首尾兩個索引
新節點的兩個索引:newStartIdx , newEndIdx
舊節點的兩個索引:oldStartIdx,oldEndIdx
以兩邊向中間包圍的形式 來進行周遊
頭部的子節點比較完畢,startIdx 就加1
尾部的子節點比較完畢,endIdex 就減1
隻要其中一個數組周遊完(startIdx<endIdx),則結束周遊
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-oaL7pEiQ-1570843827763)( https://pic1.zhimg.com/80/v2-436ec22ba4a0b626e7dd4f414684eafa_hd.png )]
源碼處理的流程分為兩個
1、比較新舊子節點
2、比較完畢,處理剩下的節點
我們來逐個說明這兩個流程
1 比較新舊子節點
注:這裡有兩個數組,一個是 新子Vnode數組,一個舊子Vnode數組
在比較過程中,不會對兩個數組進行改變(比如不會插入,不會删除其子項)
而所有比較過程中都是直接 插入删除 真實頁面DOM
我們明确一點,比較的目的是什麼?
找到 新舊子節點中 的 相同的子節點,盡量以 移動 替代 建立 去更新DOM
隻有在實在不同的情況下,才會建立
比較更新計劃步驟
首先考慮,不移動DOM
其次考慮,移動DOM
最後考慮,建立 / 删除 DOM
能不移動,盡量不移動。不行就移動,實在不行就建立
下面開始說源碼中的比較邏輯
五種比較邏輯如下
1、舊頭 == 新頭
2、舊尾 == 新尾
3、舊頭 == 新尾
4、舊尾 == 新頭
5、單個查找
來分析下這五種比較邏輯
1 舊頭 == 新頭
sameVnode(oldStartVnode, newStartVnode)
當兩個新舊的兩個頭一樣的時候,并不用做什麼處理
符合我們的步驟第一條,不移動DOM完成更新
但是看到一句,patchVnode
就是為了繼續處理這兩個相同節點的子節點,或者更新文本
因為我們不考慮多層DOM 結構,是以 新舊兩個頭一樣的話,這裡就算結束了
可以直接進行下一輪循環
newStartIdx ++ , oldStartIdx ++
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-WAa55wp2-1570843827763)( https://pic2.zhimg.com/80/v2-ad42a7d1c10051e260c05b8c8e6840ac_hd.png )]
2 舊尾 == 新尾
sameVnode(oldEndVnode, newEndVnode)
和 頭頭 相同的處理是一樣的
尾尾相同,直接跳入下個循環
newEndIdx ++ , oldEndIdx ++
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-qPAYB8wE-1570843827763)( https://pic3.zhimg.com/80/v2-3548dfcffc10efeaa00fae64be509e14_hd.png)]
3 舊頭 == 新尾
sameVnode(oldStartVnode, newEndVnode)
這步不符合 不移動DOM,是以隻能 移動DOM 了
怎麼移動?
源碼是這樣的
parentElm.insertBefore(
oldStartVnode.elm,
oldEndVnode.elm.nextSibling
);
以 新子節點的位置 來移動的,舊頭 在新子節點的 末尾
是以把 oldStartVnode 的 dom 放到 oldEndVnode 的後面
但是因為沒有把dom 放到誰後面的方法,是以隻能使用 insertBefore
即放在 oldEndVnode 後一個節點的前面
圖示是這樣的
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-vaXRusTM-1570843827764)( https://pic2.zhimg.com/80/v2-c84929b58ec7bf1bc29c56042e18349f_hd.png )]
然後更新兩個索引
oldStartIdx++,newEndIdx--
4 舊尾 == 新頭
sameVnode(oldEndVnode, newStartVnode)
同樣不符合 不移動DOM,也隻能 移動DOM 了
怎麼移動?
parentElm.insertBefore(
oldEndVnode.elm,
oldStartVnode.elm
);
把 oldEndVnode DOM 直接放到 目前 oldStartVnode.elm 的前面
圖示是這樣的
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-v4qC9bgH-1570843827764)( https://pic2.zhimg.com/80/v2-a8a8ddbb2fc2b3792158134b60d2512d_hd.png )]
然後更新兩個索引
oldEndIdx--,newStartIdx++
5 單個周遊查找
目前面四種比較邏輯都不行的時候,這是最後一種處理方法
拿 新子節點的子項,直接去 舊子節點數組中周遊,找一樣的節點出來
流程大概是
1、生成舊子節點數組以 vnode.key 為key 的 map 表
2、拿到新子節點數組中 一個子項,判斷它的key是否在上面的map 中
3、不存在,則建立DOM
4、存在,繼續判斷是否 sameVnode
下面就詳細說一下
1 生成map 表
這個map 表的作用,就主要是判斷存在什麼舊子節點
比如你的舊子節點數組是
[{
tag:"div", key:1
},{
tag:"strong", key:2
},{
tag:"span", key:4
}]
經過 createKeyToOldIdx 生成一個 map 表 oldKeyToIdx
{ vnodeKey: 數組Index }
屬性名是 vnode.key,屬性值是 該 vnode 在children 的位置
是這樣(具體源碼看上篇文章 Diff - 源碼版 之 相關輔助函數)
oldKeyToIdx = {
1:0,
2:1,
4:2
}
2 判斷 新子節點是否存在舊子節點數組中
拿到新子節點中的 子項Vnode,然後拿到它的 key
去比對map 表,判斷是否有相同節點
oldKeyToIdx[newStartVnode.key]
3 不存在舊子節點數組中
直接建立DOM,并插入oldStartVnode 前面
createElm(newStartVnode, parentElm, oldStartVnode.elm);
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-oHCzbXLA-1570843827764)( https://pic4.zhimg.com/80/v2-b5304d0a6ba1dc90bbc02ef4ba519c9b_hd.png )]
4 存在舊子節點數組中
找到這個舊子節點,然後判斷和新子節點是否 sameVnode
如果相同,直接移動到 oldStartVnode 前面
如果不同,直接建立插入 oldStartVnode 前面
我們上面說了比較子節點的處理的流程分為兩個
1、比較新舊子節點
2、比較完畢,處理剩下的節點
比較新舊子節點上面已經說完了,下面就到了另一個流程,比較剩餘的節點,詳情看下面
處理可能剩下的節點
在updateChildren 中,比較完新舊兩個數組之後,可能某個數組會剩下部分節點沒有被處理過,是以這裡需要統一處理
1 新子節點周遊完了
newStartIdx > newEndIdx
新子節點周遊完畢,舊子節點可能還有剩
是以我們要對可能剩下的舊節點進行 批量删除!
就是周遊剩下的節點,逐個删除DOM
for (; oldStartIdx <= oldEndIdx; ++oldStartIdx) {
oldCh[oldStartIdx]
.parentNode
.removeChild(el);
}
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-rw4AWwjU-1570843827765)( https://pic4.zhimg.com/80/v2-1d91dace25b34e5e6fef34d0a3a38833_hd.png )]
2舊子節點周遊完了
oldStartIdx > oldEndIdx
舊子節點周遊完畢,新子節點可能有剩
是以要對剩餘的新子節點處理
很明顯,剩餘的新子節點不存在 舊子節點中,是以全部建立
for (; newStartIdx <= newEndIdx; ++newStartIdx) {
createElm(
newCh[newStartIdx],
parentElm,
refElm
);
}
但是建立有一個問題,就是插在哪裡?
是以其中的 refElm 就成了疑點,看下源碼
var newEnd = newCh[newEndIdx + 1]
refElm = newEnd ? newEnd.elm :null;
refElm 擷取的是 newEndIdx 後一位的節點
目前沒有處理的節點是 newEndIdx
也就是說 newEndIdx+1 的節點如果存在的話,肯定被處理過了
如果 newEndIdx 沒有移動過,一直是最後一位,那麼就不存在 newCh[newEndIdx + 1]
那麼 refElm 就是空,那麼剩餘的新節點 就全部添加進 父節點孩子的末尾,相當于
for (; newStartIdx <= newEndIdx; ++newStartIdx) {
parentElm.appendChild(
newCh[newStartIdx]
);
}
如果 newEndIdx 移動過,那麼就逐個添加在 refElm 的前面,相當于
for (; newStartIdx <= newEndIdx; ++newStartIdx) {
parentElm.insertBefore(
newCh[newStartIdx] ,
refElm
);
}
如圖
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-tHXZ3TQW-1570843827765)( https://pic4.zhimg.com/80/v2-2458ff6c8577012eb9505dd9e5729f92_hd.png )]
思考為什麼這麼比較
我們已經講完了所有 Diff 的内容,大家也應該能領悟到 Diff 的思想
但是我強迫自己去思考一個問題,就是
為什麼會這樣去比較?
以下純屬個人意淫想法,沒有權威認證,僅供參考
我們所有的比較,都是為了找到 新子節點 和 舊子節點 一樣的子節點
而且我們的比較處理的宗旨是
1、能不移動,盡量不移動
2、沒得辦法,隻好移動
3、實在不行,建立或删除
首先,一開始比較,肯定是按照我們的第一宗旨 不移動 ,找到可以不移動的節點
而 頭頭,尾尾比較 符合我們的第一宗旨,是以出現在最開始,嗯,這個可以想通
然後就到我們的第二宗旨 移動,按照 updateChildren 的做法有
舊頭新尾比較,舊尾新頭比較,單個查找比較
我開始疑惑了,咦?頭尾比較為了移動我知道,但是為什麼要出現這種比較?
明明我可以用 單個查找 的方式,完成所有的移動操作啊?
我思考了很久,頭和尾的關系,覺得可能是為了避免極端情況的消耗??
怎麼說?
比如當我們去掉頭尾比較,全部使用單個查找的方式
如果出現頭 和 尾 節點一樣的時候,一個節點需要周遊 從頭找到尾 才能找到相同節點
這樣實在是太消耗了,是以這裡加入了 頭尾比較 就是為了排除 極端情況造成的消耗操作
當然,這隻是我個人的想法,僅供參考,雖然這麼說,我也的确做了個例子測試
子節點中加入了出現兩個頭尾比較情況的子項 b div
oldCh = ['header','span','div','b']
newCh = ['sub','b','div','strong']
使用 Vue 去更新,比較更新速度,然後更新十次,計算平均值
1、全用 單個查找,用時 0.91ms
2、加入頭尾比較,用時 0.853ms
的确是快一些喔
走流程
我相信經過這麼長的一篇文章,大家的腦海中還沒有把所有的知識點集合起來,可能對整個流程還有點模糊
沒事,我們現在就來舉一個例子,一步步走流程,完成更新
以下的節點,綠色表示未處理,灰色表示已經處理,淡綠色表示正在處理,紅色表示新插入,如下
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-m6rs3dA5-1570843827766)( https://pic3.zhimg.com/80/v2-9041886ec653b49b1d164ada9fca6002_hd.png )]
現在Vue 需要更新,存在下面兩組新舊子節點,需要進行比較,來判斷需要更新哪些節點
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-ZkRh3PJM-1570843827766)( https://pic4.zhimg.com/80/v2-b307e247e02fef84a0b05e1315fd2e75_hd.png )]
1頭頭比較,節點一樣,不需移動,隻用更新索引
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-m9tWpiWU-1570843827766)( https://pic1.zhimg.com/80/v2-b43e330eeb5b8f44cf84f4416b9cdd95_hd.png )]
更新索引,newStartIdx++ , oldStartIdx++
開始下輪處理
一系列判斷之後,【舊頭 2】 和 【 新尾 2】相同,直接移動到 oldEndVnode 後面
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-uPv5t7H8-1570843827766)( https://pic1.zhimg.com/80/v2-93e5bb51edb631517797305ba02df449_hd.png )]
更新索引,newEndIdx-- ,oldStartIdx ++
開始下輪處理
3一系列判斷之後,【舊頭 2】 和 【 新尾 2】相同,直接移動到 oldStartVnode 前面
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-LEVEVtjb-1570843827767)( https://pic1.zhimg.com/80/v2-2d145dd1efaee4238ecffe099948bd7d_hd.png )]
更新索引,oldEndIdx-- ,newStartIdx++
開始下輪比較
4隻剩一個節點,走到最後一個判斷,單個查找
找不到一樣的,直接建立插入到 oldStartVnode 前面
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-4Sw8SLaT-1570843827767)( https://pic4.zhimg.com/80/v2-0e540cc8605702eb00600235c66e6225_hd.png )]
更新索引,newStartIdx++
此時 newStartIdx> newEndIdx ,結束循環
5 批量删除可能剩下的老節點
此時看 舊 Vnode 數組中, oldStartIdx 和 oldEndIdx 都指向同一個節點,是以隻用删除 oldVnode-4 這個節點
ok,完成所有比較流程
耶,Diff 内容講完了,謝謝大家的觀看
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-nMLeXZMu-1570843827767)( https://pic1.zhimg.com/80/v2-3b85bc8c144fb58033d1ef5bf001f312_hd.gif )]
最後
鑒于本人能力有限,難免會有疏漏錯誤的地方,請大家多多包涵,如果有任何描述不當的地方,歡迎背景聯系本人,有重謝