Vue虛擬Dom

虛擬dom:vdom是樹狀結(jié)構(gòu),其節(jié)點(diǎn)為vnode,vnode和瀏覽器DOM中的Node一一對(duì)應(yīng),通過vnode的el屬性可以訪問到對(duì)應(yīng)的Node。

虛擬DOM就是為了解決瀏覽器性能問題而被設(shè)計(jì)出來的。如前,若一次操作中有10次更新DOM的動(dòng)作,虛擬DOM不會(huì)立即操作DOM,而是將這10次更新的diff內(nèi)容保存到本地一個(gè)JS對(duì)象中,最終將這個(gè)JS對(duì)象一次性attch到DOM樹上,再進(jìn)行后續(xù)操作,避免大量無謂的計(jì)算量。所以,用JS對(duì)象模擬DOM節(jié)點(diǎn)的好處是,頁面的更新可以先全部反映在JS對(duì)象(虛擬DOM)上,操作內(nèi)存中的JS對(duì)象的速度顯然要更快,等更新完成后,再將最終的JS對(duì)象映射成真實(shí)的DOM,交由瀏覽器去繪制。

vdom因?yàn)槭羌兇獾腏S對(duì)象,所以操作它會(huì)很高效,但是vdom的變更最終會(huì)轉(zhuǎn)換成DOM操作,為了實(shí)現(xiàn)高效的DOM操作,一套高效的虛擬DOM diff算法顯得很有必要。

diff算法

Vue和React的diff算法相同,僅在同級(jí)的vnode間做diff,遞歸地進(jìn)行同級(jí)vnode的diff,最終實(shí)現(xiàn)整個(gè)DOM樹的更新。

  1. 簡(jiǎn)單diff算法:逐個(gè)遍歷newVdom的節(jié)點(diǎn),找到它在oldVdom中的位置,如果找到了就移動(dòng)對(duì)應(yīng)的DOM元素,如果沒找到說明是新增節(jié)點(diǎn),則新建一個(gè)節(jié)點(diǎn)插入。遍歷完成之后如果oldVdom中還有沒處理過的節(jié)點(diǎn),則說明這些節(jié)點(diǎn)在newVdom中被刪除了,刪除它們即可。
  2. image.png

如上圖的例子,更新前是1到10排列的Node列表,更新后是亂序排列的Node列表。羅列一下圖中有以下幾種類型的節(jié)點(diǎn)變化情況:

(1)、頭部相同、尾部相同的節(jié)點(diǎn):如1、10
(2)、頭尾相同的節(jié)點(diǎn):如2、9(處理完頭部相同、尾部相同節(jié)點(diǎn)之后)
(3)、新增的節(jié)點(diǎn):11
(4)、刪除的節(jié)點(diǎn):8
(5)、其他節(jié)點(diǎn):3、4、5、6、7

四、Vue的diff實(shí)現(xiàn)

上圖例子中畫上了oldStart+oldEnd,newStart+newEnd這樣2對(duì)指針,分別對(duì)應(yīng)oldVdom和newVdom的起點(diǎn)和終點(diǎn)。起止點(diǎn)之前的節(jié)點(diǎn)是待處理的節(jié)點(diǎn),Vue不斷對(duì)vnode進(jìn)行處理同時(shí)移動(dòng)指針直到其中任意一對(duì)起點(diǎn)和終點(diǎn)相遇。處理過的節(jié)點(diǎn)Vue會(huì)在oldVdom和newVdom中同時(shí)將它標(biāo)記為已處理(標(biāo)記方法后文中有介紹)。Vue通過以下措施來提升diff的性能。

  1. 優(yōu)先處理特殊場(chǎng)景

(1)、頭部的同類型節(jié)點(diǎn)、尾部的同類型節(jié)點(diǎn)
這類節(jié)點(diǎn)更新前后位置沒有發(fā)生變化,所以不用移動(dòng)它們對(duì)應(yīng)的DOM
(2)、頭尾/尾頭的同類型節(jié)點(diǎn)
這類節(jié)點(diǎn)位置很明確,不需要再花心思查找,直接移動(dòng)DOM就好

處理了這些場(chǎng)景之后,一方面一些不需要做移動(dòng)的DOM得到快速處理,另一方面待處理節(jié)點(diǎn)變少,縮小了后續(xù)操作的處理范圍,性能也得到提升

  1. “原地復(fù)用”

“原地復(fù)用”是指Vue會(huì)盡可能復(fù)用DOM,盡可能不發(fā)生DOM的移動(dòng)。Vue在判斷更新前后指針是否指向同一個(gè)節(jié)點(diǎn),其實(shí)不要求它們真實(shí)引用同一個(gè)DOM節(jié)點(diǎn),實(shí)際上它僅判斷指向的是否是同類節(jié)點(diǎn)(比如2個(gè)不同的div,在DOM上它們是不一樣的,但是它們屬于同類節(jié)點(diǎn)),如果是同類節(jié)點(diǎn),那么Vue會(huì)直接復(fù)用DOM,這樣的好處是不需要移動(dòng)DOM。再看上面的實(shí)例,假如10個(gè)節(jié)點(diǎn)都是div,那么整個(gè)diff過程中就沒有移動(dòng)DOM的操作了。

  1. 先看一張整體視圖,整個(gè)diff分兩部分:


    image.png

(1)、第一部分是一個(gè)循環(huán),循環(huán)內(nèi)部是一個(gè)分支邏輯,每次循環(huán)只會(huì)進(jìn)入其中的一個(gè)分支,每次循環(huán)會(huì)處理一個(gè)節(jié)點(diǎn),處理之后將節(jié)點(diǎn)標(biāo)記為已處理(oldVdom和newVdom都要進(jìn)行標(biāo)記,如果節(jié)點(diǎn)只出現(xiàn)在其中某一個(gè)vdom中,則另一個(gè)vdom中不需要進(jìn)行標(biāo)記),標(biāo)記的方法有2種,當(dāng)節(jié)點(diǎn)正好在vdom的指針處,移動(dòng)指針將它排除到未處理列表之外即可,否則就要采用其他方法,Vue的做法是將節(jié)點(diǎn)設(shè)置為undefined。

(2)、循環(huán)結(jié)束之后,可能newVdom或者oldVdom中還有未處理的節(jié)點(diǎn),如果是newVdom中有未處理節(jié)點(diǎn),則這些節(jié)點(diǎn)是新增節(jié)點(diǎn),做新增處理。如果是oldVdom中有這類節(jié)點(diǎn),則這些是需要?jiǎng)h除的節(jié)點(diǎn),相應(yīng)在DOM樹中刪除之

整個(gè)過程是逐步找到更新前后vdom的差異,然后將差異反應(yīng)到DOM樹上(也就是patch),特別要提一下Vue的patch是即時(shí)的,并不是打包所有修改最后一起操作DOM(React則是將更新放入隊(duì)列后集中處理),朋友們會(huì)問這樣做性能很差吧?實(shí)際上現(xiàn)代瀏覽器對(duì)這樣的DOM操作做了優(yōu)化,并無差別。

  1. 我們會(huì)有兩個(gè)虛擬DOM(js對(duì)象,new/old進(jìn)行比較diff),用戶交互我們操作數(shù)據(jù)變化new虛擬DOM,old虛擬DOM會(huì)映射成實(shí)際DOM(js對(duì)象生成的DOM文檔)通過DOM fragment操作給瀏覽器渲染。當(dāng)修改new虛擬DOM,會(huì)把newDOM和oldDOM通過diff算法比較,得出diff結(jié)果數(shù)據(jù)表(用4種變換情況表示)。再把diff結(jié)果表通過DOM fragment更新到瀏覽器DOM中。

虛擬DOM的存在的意義?

vdom 的真正意義是為了實(shí)現(xiàn)跨平臺(tái),服務(wù)端渲染,以及提供一個(gè)性能還算不錯(cuò) Dom 更新策略。vdom 讓整個(gè) mvvm 框架靈活了起來Diff算法只是為了虛擬DOM比較替換效率更高,通過Diff算法得到diff算法結(jié)果數(shù)據(jù)表(需要進(jìn)行哪些操作記錄表)。原本要操作的DOM在vue這邊還是要操作的,只不過用到了js的DOM fragment來操作dom(統(tǒng)一計(jì)算出所有變化后統(tǒng)一更新一次DOM)進(jìn)行瀏覽器DOM一次性更新。其實(shí)DOM fragment我們不用平時(shí)發(fā)開也能用,但是這樣程序員寫業(yè)務(wù)代碼就用把DOM操作放到fragment里,這就是框架的價(jià)值,程序員才能專注于寫業(yè)務(wù)代碼。

注:了解一些基礎(chǔ)的,主要參考一下博客
http://www.lxweimin.com/p/af0b398602bc
https://blog.csdn.net/m6i37jk/article/details/78140159

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容