javascript基礎修煉(10)——VirtualDOM和基本DFS
2020-04-10

1. Virtual-DOM是什么

Virtual-DOM,即虛擬DOM樹。瀏覽器在解析文件時,會將html文檔轉換為document對象,在瀏覽器環境中運行的腳本文件都可以獲取到它,通過操作document對象暴露的接口可以直接操作頁面上的DOM節點。但是DOM讀寫是非常耗性能的,很容易觸發不必要的重繪和重排,為了更好地處理DOM操作,Virtual-DOM技術就誕生了。Virtual-DOM就是在javascript中模擬真實DOM的結構,通過數據追蹤和狀態對比來減少對于真實DOM的操作,以此來提高程序的效率的一種技術。

Virtual-DOM技術是前端高性能的基石,它是真實document對象的抽象,通過對比新舊Virtual-DOM的區別,找出發生變化的DOM節點,再利用算法得到相對更合理的DOM節點修改方案,最終再將方案應用在document對象上來改變頁面的展示內容。

主流前端SPA框架都離不開【Virtual-DOM模型 + DOM-Diff算法 + 生命周期鉤子】這樣的核心模型。

2. Virtual-DOM的基本結構

在上一篇博文《javascript基礎修煉(9)——MVVM中雙向數據綁定的基本原理》中,我們通過document.getElementById()從真實DOM中獲得了帶有自定義屬性的待解析結構,這里是有一些問題的,實際的過程是先解析模板字符串得到虛擬DOM樹,最后生成真實的DOM樹。

實際上我們在使用SPA框架時所編寫的html模板,并沒有被直接當做DOM片段加載到頁面上使用,而是將文件當做字符串讀入到程序中,然后通過解析來生成Virtual-DOM樹,接著通過SPA框架的渲染函數來生成必要的片段后才生成真實的DOM節點。例如我們要生成下文示例的HTML片段(為了方便演示,示例中只涉及了類名和文本節點):

<body > <div > <ul > <li >sidebar-1</li> <li >sidebar-2</li> <li >sidebar-3</li> </ul> </div> <div > <div >header-zone</div> <div >core-content</div> <div >footer-zone</div> </div> <div >暫未開發</div></body>

我們需要構建出一個簡易模型來表達上面的結構:

virtualDom = { name:"body", props:{ className:"main" }, children:[{ name:"div", props:{...}, children:[...] },{ name:"div", props:{...}, children:[...] },{ name:"div", props:{...}, children:[...] }]}

建立一個生成虛擬節點的輔助函數:

//構建DOM節點的輔助函數function h(name, props, children) { return { name:name, props:props, children:children }}//手動生成virtual-DOMvar tree = h("body",{className:"main"},[ h("div",{className:"sideBar"},[ h("ul",{className:"sideBarContainer"},[ h("li",{className:"sideBarItem"},["sidebar-1"]), h("li",{className:"sideBarItem"},["sidebar-2"]), h("li",{className:"sideBarItem"},["sidebar-3"]), ]) ]), h("div",{className:"mainContent"},[ h("div",{className:"header"},["header-zone"]), h("div",{className:"coreContent"},["core-content"]), h("div",{className:"footer"},["footer-zone"]), ]), h("div",{className:"rightSide"},["暫未開發"]) ]);

通過上面的方法得到的tree對象就涵蓋了模板片段中的結構和關鍵信息。實際開發中并不需要像上面一樣手動來填寫DOM結構,可以將模板字符串掛載到離線DOM節點上,然后在遞歸解析的同時來構建Virtual-DOM就可以了。

3. 使用DFS從Virtual-DOM生成DOM

至此我們完成了模板的編譯,也得到了Virtual-DOM對象,但它似乎并沒有什么用處,畢竟我們已經完成了對模板的解析,渲染出頁面沒什么問題,其實Virtual-DOM對于首屏來說并沒有什么特別重要的意義,它的價值在模型和視圖發生變化時才會體現。上一篇博文的末尾我們已經提到了更新視圖時的效率問題,當數據模型發生變化后,我們需要一個方法來收集所有需要修改的DOM,并為之提供高效的修改方式(你總不能一有變化就把整個網頁重新渲染,或者讓數據模型各自去修改各自綁定的DOM吧)。那么為了能夠收集所有DOM節點的變化,我們就需要遍歷所有節點。

對數據結構和算法有一定了解的讀者很容易想到,遍歷解析一個Virtual-DOM實際上就是對其進行先序深度優先遍歷(Pre-Order Depth-First-Search),本節中,我們先預熱一下,使用這種方式來復現一下DOM結構。

function dfswalking(tree) { var _childrenLength; //執行動作 if (typeof tree.children[0] === "string") { console.log(`<${tree.name} >${tree.children[0]}</${tree.name}>`); } else { console.log(`<${tree.name} > -->`); for(var i = 0, _childrenLength = tree.children.length; i < _childrenLength; i++){ dfswalking(tree.children[i]); } }}

本例中僅打印出字符串的方式來展示,可以在控制臺看到輸出結果:

下一篇博文中將分析如何通過domDiff(oldTree, newTree)的方法通過同樣的遍歷方法來收集變化并批量更新視圖。

4. 聲明

本篇只是部分原理的學習筆記,并不代表框架真實源碼的實現邏輯。

体彩云南十一选五下载