1. Virtual DOM是什麼 Virtual DOM,即虛擬DOM樹。瀏覽器在解析文件時,會將 文檔轉換為 對象,在瀏覽器環境中運行的腳本文件都可以獲取到它,通過操作 對象暴露的介面可以直接操作頁面上的DOM節點。但是DOM讀寫是非常耗性能的,很容易觸發不必要的重繪和重排,為了更好地處理DOM ...
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 class="main">
<div class="sideBar">
<ul class="sideBarContainer">
<li class="sideBarItem">sidebar-1</li>
<li class="sideBarItem">sidebar-2</li>
<li class="sideBarItem">sidebar-3</li>
</ul>
</div>
<div class="mainContent">
<div class="header">header-zone</div>
<div class="coreContent">core-content</div>
<div class="footer">footer-zone</div>
</div>
<div class="rightSide">暫未開發</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-DOM
var 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} class="${tree.props.className}">${tree.children[0]}</${tree.name}>`);
} else {
console.log(`<${tree.name} class="${tree.props.className}"> -->`);
for(var i = 0, _childrenLength = tree.children.length; i < _childrenLength; i++){
dfswalking(tree.children[i]);
}
}
}
本例中僅列印出字元串的方式來展示,可以在控制台看到輸出結果:
下一篇博文中將分析如何通過domDiff(oldTree, newTree)
的方法通過同樣的遍歷方法來收集變化並批量更新視圖。
4. 聲明
本篇只是部分原理的學習筆記,並不代表框架真實源碼的實現邏輯。