vue核心之虛擬DOM(vdom)


一、真實DOM和其解析流程?

    瀏覽器渲染引擎工作流程都差不多,大致分為5步,創建DOM樹——創建StyleRules——創建Render樹——布局Layout——繪制Painting

    第一步,用HTML分析器,分析HTML元素,構建一顆DOM樹(標記化和樹構建)。

    第二步,用CSS分析器,分析CSS文件和元素上的inline樣式,生成頁面的樣式表。

    第三步,將DOM樹和樣式表,關聯起來,構建一顆Render樹(這一過程又稱為Attachment)。每個DOM節點都有attach方法,接受樣式信息,返回一個render對象(又名renderer)。這些render對象最終會被構建成一顆Render樹。

    第四步,有了Render樹,瀏覽器開始布局,為每個Render樹上的節點確定一個在顯示屏上出現的精確坐標。

    第五步,Render樹和節點顯示坐標都有了,就調用每個節點paint方法,把它們繪制出來。 

    DOM樹的構建是文檔加載完成開始的?構建DOM數是一個漸進過程,為達到更好用戶體驗,渲染引擎會盡快將內容顯示在屏幕上。它不必等到整個HTML文檔解析完畢之后才開始構建render數和布局。

    Render樹是DOM樹和CSSOM樹構建完畢才開始構建的嗎?這三個過程在實際進行的時候又不是完全獨立,而是會有交叉。會造成一邊加載,一遍解析,一遍渲染的工作現象。

    CSS的解析是從右往左逆向解析的(從DOM樹的下-上解析比上-下解析效率高),嵌套標簽越多,解析越慢。

 
webkit渲染引擎工作流程

二、JS操作真實DOM的代價!

        用我們傳統的開發模式,原生JS或JQ操作DOM時,瀏覽器會從構建DOM樹開始從頭到尾執行一遍流程。在一次操作中,我需要更新10個DOM節點,瀏覽器收到第一個DOM請求后並不知道還有9次更新操作,因此會馬上執行流程,最終執行10次。例如,第一次計算完,緊接着下一個DOM更新請求,這個節點的坐標值就變了,前一次計算為無用功。計算DOM節點坐標值等都是白白浪費的性能。即使計算機硬件一直在迭代更新,操作DOM的代價仍舊是昂貴的,頻繁操作還是會出現頁面卡頓,影響用戶體驗。

三、為什么需要虛擬DOM,它有什么好處?

        Web界面由DOM樹(樹的意思是數據結構)來構建,當其中一部分發生變化時,其實就是對應某個DOM節點發生了變化,

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

四、實現虛擬DOM

        例如一個真實的DOM節點。

 
真實DOM

        我們用JS來模擬DOM節點實現虛擬DOM。

 
虛擬DOM

        其中的Element方法具體怎么實現的呢?

 
Element方法實現

        第一個參數是節點名(如div),第二個參數是節點的屬性(如class),第三個參數是子節點(如ul的li)。除了這三個參數會被保存在對象上外,還保存了key和count。其相當於形成了虛擬DOM樹。

 
虛擬DOM樹

        有了JS對象后,最終還需要將其映射成真實DOM

 
虛擬DOM對象映射成真實DOM

        我們已經完成了創建虛擬DOM並將其映射成真實DOM,這樣所有的更新都可以先反應到虛擬DOM上,如何反應?需要用到Diff算法

        兩棵樹如果完全比較時間復雜度是O(n^3),但參照《深入淺出React和Redux》一書中的介紹,React的Diff算法的時間復雜度是O(n)。要實現這么低的時間復雜度,意味着只能平層的比較兩棵樹的節點,放棄了深度遍歷。這樣做,似乎犧牲掉了一定的精確性來換取速度,但考慮到現實中前端頁面通常也不會跨層移動DOM元素,這樣做是最優的。

        深度優先遍歷,記錄差異

        。。。。

        Diff操作

        在實際代碼中,會對新舊兩棵樹進行一個深度的遍歷,每個節點都會有一個標記。每遍歷到一個節點就把該節點和新的樹進行對比,如果有差異就記錄到一個對象中。

        下面我們創建一棵新樹,用於和之前的樹進行比較,來看看Diff算法是怎么操作的。

 
old Tree
 
new Tree

        平層Diff,只有以下4種情況:

        1、節點類型變了,例如下圖中的P變成了H3。我們將這個過程稱之為REPLACE。直接將舊節點卸載並裝載新節點。舊節點包括下面的子節點都將被卸載,如果新節點和舊節點僅僅是類型不同,但下面的所有子節點都一樣時,這樣做效率不高。但為了避免O(n^3)的時間復雜度,這樣是值得的。這也提醒了開發者,應該避免無謂的節點類型的變化,例如運行時將div變成p沒有意義。

        2、節點類型一樣,僅僅屬性或屬性值變了。我們將這個過程稱之為PROPS。此時不會觸發節點卸載和裝載,而是節點更新。

 
查找不同屬性方法

        3、文本變了,文本對也是一個Text Node,也比較簡單,直接修改文字內容就行了,我們將這個過程稱之為TEXT

        4、移動/增加/刪除 子節點,我們將這個過程稱之為REORDER。看一個例子,在A、B、C、D、E五個節點的B和C中的BC兩個節點中間加入一個F節點。

 
例子

        我們簡單粗暴的做法是遍歷每一個新虛擬DOM的節點,與舊虛擬DOM對比相應節點對比,在舊DOM中是否存在,不同就卸載原來的按上新的。這樣會對F后邊每一個節點進行操作。卸載C,裝載F,卸載D,裝載C,卸載E,裝載D,裝載E。效率太低。

 
粗暴做法

        如果我們在JSX里為數組或枚舉型元素增加上key后,它能夠根據key,直接找到具體位置進行操作,效率比較高。常見的最小編輯距離問題,可以用Levenshtein Distance算法來實現,時間復雜度是O(M*N),但通常我們只要一些簡單的移動就能滿足需要,降低精確性,將時間復雜度降低到O(max(M,N))即可。

 
最終Diff出來的結果

映射成真實DOM

        虛擬DOM有了,Diff也有了,現在就可以將Diff應用到真實DOM上了。深度遍歷DOM將Diff的內容更新進去。

 
根據Diff更新DOM
 
根據Diff更新DOM

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

虛擬DOM的存在的意義?vdom 的真正意義是為了實現跨平台,服務端渲染,以及提供一個性能還算不錯 Dom 更新策略。vdom 讓整個 mvvm 框架靈活了起來

Diff算法只是為了虛擬DOM比較替換效率更高,通過Diff算法得到diff算法結果數據表(需要進行哪些操作記錄表)。原本要操作的DOM在vue這邊還是要操作的,只不過用到了js的DOM fragment來操作dom(統一計算出所有變化后統一更新一次DOM)進行瀏覽器DOM一次性更新。其實DOM fragment我們不用平時發開也能用,但是這樣程序員寫業務代碼就用把DOM操作放到fragment里,這就是框架的價值,程序員才能專注於寫業務代碼

轉載:https://www.jianshu.com/p/af0b398602bc


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM