今天我們了解一下vue中批量異步更新策略和虛擬DOM以及Diff算法
異步更新策略
1、update() core\observer\watcher.js dep.notify()之后watcher執(zhí)行更新,執(zhí)行入隊(duì)操作
由此,我們找到對(duì)應(yīng)的文件。

由圖片我們發(fā)現(xiàn)一個(gè)watcher入隊(duì)操作。點(diǎn)進(jìn)去會(huì)跳到core\observer\scheduler.js文件。

vue會(huì)判斷watcher是否已經(jīng)入隊(duì),如果已經(jīng)入隊(duì),就不再入隊(duì)。然后點(diǎn)進(jìn)nextTIck,我們就能看到nextTick()是如何刷新隊(duì)列的工作原理。
3、進(jìn)入nextTicke()函數(shù)。

4、點(diǎn)進(jìn)timerFunc(),啟動(dòng)異步任務(wù)的函數(shù)。

會(huì)發(fā)現(xiàn),調(diào)用異步任務(wù)的方式,首選是promise
異步操作,如果用戶所使用的設(shè)備不支持promise異步,則使用MutationObserver、如果MutationObserver不行則使用setImmediate、如果setImmediate也不能使用,最后就會(huì)使用宏任務(wù)setTimeout方式進(jìn)行更新。
5、回到nextTick(flushSchedulerQueue),點(diǎn)擊flushSchedulerQueue隊(duì)列。會(huì)看到一個(gè)run()方法是執(zhí)行真正的更新方法。

6、在run方法中有一個(gè)get()方法,我們點(diǎn)進(jìn)去查看。

7、進(jìn)入到get()方法里面,我們會(huì)發(fā)現(xiàn),執(zhí)行了一個(gè)getter()函數(shù)。

8、查看getter方法會(huì)發(fā)現(xiàn),getter是一個(gè)被賦值的函數(shù),這個(gè)時(shí)候我們就需要從vue初始化查看watcher。

9、知道src/core/instance/lifecycle.js文件,查看一下組件初始化。會(huì)發(fā)現(xiàn),在執(zhí)行mountComponent()組件初始化時(shí),會(huì)創(chuàng)建一個(gè)組件更新函數(shù)updateComponent,以及創(chuàng)建 組件級(jí)watcher。在創(chuàng)建watcher時(shí)會(huì)把updateComponent更新函數(shù)傳進(jìn)去。


至此,我們就可以梳理出來(lái)整個(gè)更新流程了。
流程如下:當(dāng)有組件內(nèi)部有數(shù)據(jù)進(jìn)行更新時(shí) => set() => dep.notify() => queueWatcher => nextTick(flushSchedulerQueue) => flushSchedulerQueue => watcher.run() => updateComponent => render => update => patch
nextTtick:做的事情就是把回到函數(shù)放進(jìn)到callbacks隊(duì)列中。
diff算法:
vue中主要使用的是patch函數(shù)進(jìn)行diff算法。文件路徑:core\vdom\patch.js。
diff算法的原則是:同級(jí)比較深度優(yōu)先。
1、首先進(jìn)行樹(shù)級(jí)別比較,可能有三種情況:增刪改。
new VNode不存在就刪;
old VNode不存在就增;
都存在就執(zhí)行diff執(zhí)行更新
2、patchVnode
比較兩個(gè)VNode,包括三種類型操作:屬性更新、文本更新、子節(jié)點(diǎn)更新
具體規(guī)則如下:
- 新老節(jié)點(diǎn)均有children子節(jié)點(diǎn),則對(duì)子節(jié)點(diǎn)進(jìn)行diff操作,調(diào)用updateChildren
- 如果新節(jié)點(diǎn)有子節(jié)點(diǎn)而老節(jié)點(diǎn)沒(méi)有子節(jié)點(diǎn),先清空老節(jié)點(diǎn)的文本內(nèi)容,然后為其新增子節(jié)點(diǎn)。
- 當(dāng)新節(jié)點(diǎn)沒(méi)有子節(jié)點(diǎn)而老節(jié)點(diǎn)有子節(jié)點(diǎn)的時(shí)候,則移除該節(jié)點(diǎn)的所有子節(jié)點(diǎn)。
- 當(dāng)新老節(jié)點(diǎn)都無(wú)子節(jié)點(diǎn)的時(shí)候,只是文本的替換。
3、updateChildren
在新老兩組VNode節(jié)點(diǎn)的左右頭尾兩側(cè)都有一個(gè)變量標(biāo)記,在遍歷過(guò)程中這幾個(gè)變量都會(huì)向中間靠攏。 當(dāng)oldStartIdx > oldEndIdx或者newStartIdx > newEndIdx時(shí)結(jié)束循環(huán)。
首先,oldStartVnode、oldEndVnode與newStartVnode、newEndVnode兩兩交叉比較,共有4種比較方法。
1、當(dāng) oldStartVnode和newStartVnode 或者 oldEndVnode和newEndVnode 滿足sameVnode,直接將該VNode節(jié)點(diǎn)進(jìn)行patchVnode即可,不需再遍歷就完成了一次循環(huán)。
2、如果oldStartVnode與newEndVnode滿足sameVnode。說(shuō)明oldStartVnode已經(jīng)跑到了oldEndVnode后面去了,進(jìn)行patchVnode的同時(shí)還需要將真實(shí)DOM節(jié)點(diǎn)移動(dòng)到oldEndVnode的后面。
3、如果oldEndVnode與newStartVnode滿足sameVnode,說(shuō)明oldEndVnode跑到了oldStartVnode的前面,進(jìn)行patchVnode的同時(shí)要將oldEndVnode對(duì)應(yīng)DOM移動(dòng)到oldStartVnode對(duì)應(yīng)DOM的前面。
4、如果以上情況均不符合,則在old VNode中找與newStartVnode相同的節(jié)點(diǎn),若存在執(zhí)行
patchVnode,同時(shí)將elmToMove移動(dòng)到oldStartIdx對(duì)應(yīng)的DOM的前面。
當(dāng)然也有可能newStartVnode在old VNode節(jié)點(diǎn)中找不到一致的sameVnode,這個(gè)時(shí)候會(huì)調(diào)用
createElm創(chuàng)建一個(gè)新的DOM節(jié)點(diǎn)。
5、最后進(jìn)行掃尾的批量刪除或者批量新增操作
當(dāng)結(jié)束時(shí)oldStartIdx > oldEndIdx,這個(gè)時(shí)候舊的VNode節(jié)點(diǎn)已經(jīng)遍歷完了,但是新的節(jié)點(diǎn)還沒(méi)有。說(shuō)明了新的VNode節(jié)點(diǎn)實(shí)際上比老的VNode節(jié)點(diǎn)多,需要將剩下的VNode對(duì)應(yīng)的DOM插入到真實(shí)DOM中,此時(shí)調(diào)用addVnodes(批量調(diào)用createElm接口)。
但是,當(dāng)結(jié)束時(shí)newStartIdx > newEndIdx時(shí),說(shuō)明新的VNode節(jié)點(diǎn)已經(jīng)遍歷完了,但是老的節(jié)點(diǎn)還有剩余,需要從文檔中刪 的節(jié)點(diǎn)刪除。
key的作用
判斷兩個(gè)vnode是否相同節(jié)點(diǎn),必要條件之一。
我們找到core\vdom\patch.js文件,有一個(gè)patch函數(shù)。

有一個(gè)更新patchVnode函數(shù)。點(diǎn)進(jìn)去查看。

再patchVnode函數(shù)中,可以看到先進(jìn)行屬性更新,然后進(jìn)行文本更新、最后進(jìn)行子節(jié)點(diǎn)的更新。再點(diǎn)進(jìn)去查看updateChildren函數(shù)。


updateChildren是diff算法發(fā)生的地方,就是回創(chuàng)建四個(gè)游標(biāo),進(jìn)行四種情況的判斷,最后進(jìn)行掃尾工作。具體代碼看圖片注釋。
附:vue是如何判斷一個(gè)節(jié)點(diǎn)是否相同的。
再patch文件下:找到sameVnode函數(shù),如下圖:

具體步驟如下:
1、判斷key是否相同。
2、判斷是否是相同元素。
3、判斷數(shù)據(jù)是否相同。
4、判斷input的屬性是否相同。