vue2.0源碼解析(中)

今天我們了解一下vue中批量異步更新策略和虛擬DOM以及Diff算法

異步更新策略

1、update() core\observer\watcher.js dep.notify()之后watcher執(zhí)行更新,執(zhí)行入隊(duì)操作
由此,我們找到對(duì)應(yīng)的文件。


WeChat2ee892be29fa2890da2bc96cb706452a.png

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


WeChatb693342c16fe5b3734906c465f186640.png

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


WeChatce626d454ae127b4f08e45f13eb314d0.png

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í)行真正的更新方法。


WeChat231d39962f5aa7a075c3d343294fda4c.png

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


WeChat76c183a774441a957431da5a6de2756f.png

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

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

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)去。


WeChat41b1481d41c0c53beaa98ac4f086cce2.png

WeChatf67af105886ad56cb51156d1f2687349.png

至此,我們就可以梳理出來(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ī)則如下:

  1. 新老節(jié)點(diǎn)均有children子節(jié)點(diǎn),則對(duì)子節(jié)點(diǎn)進(jìn)行diff操作,調(diào)用updateChildren
  2. 如果新節(jié)點(diǎn)有子節(jié)點(diǎn)而老節(jié)點(diǎn)沒(méi)有子節(jié)點(diǎn),先清空老節(jié)點(diǎn)的文本內(nèi)容,然后為其新增子節(jié)點(diǎn)。
  3. 當(dāng)新節(jié)點(diǎn)沒(méi)有子節(jié)點(diǎn)而老節(jié)點(diǎn)有子節(jié)點(diǎn)的時(shí)候,則移除該節(jié)點(diǎn)的所有子節(jié)點(diǎn)。
  4. 當(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ù)。


WeChat96eaf7dafce1b3c9d3b69a030dc3f5b6.png

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


WeChatb3b9082f6ca9e8c3dd810a710e22e2b2.png

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

WeChat3cecca2920726d6518dd179dcd54d010.png

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

具體步驟如下:
1、判斷key是否相同。
2、判斷是否是相同元素。
3、判斷數(shù)據(jù)是否相同。
4、判斷input的屬性是否相同。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容