創(chuàng)新互聯(lián)建站于2013年開始,先為南潯等服務(wù)建站,南潯等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為南潯企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。
Virtual-DOM,即虛擬DOM樹。瀏覽器在解析文件時(shí),會(huì)將html
文檔轉(zhuǎn)換為document
對(duì)象,在瀏覽器環(huán)境中運(yùn)行的腳本文件都可以獲取到它,通過操作document
對(duì)象暴露的接口可以直接操作頁面上的DOM節(jié)點(diǎn)。但是DOM讀寫是非常耗性能的,很容易觸發(fā)不必要的重繪和重排,為了更好地處理DOM操作,Virtual-DOM
技術(shù)就誕生了。Virtual-DOM
就是在javascript中模擬真實(shí)DOM的結(jié)構(gòu),通過數(shù)據(jù)追蹤和狀態(tài)對(duì)比來減少對(duì)于真實(shí)DOM的操作,以此來提高程序的效率的一種技術(shù)。
Virtual-DOM
技術(shù)是前端高性能的基石,它是真實(shí)document
對(duì)象的抽象,通過對(duì)比新舊Virtual-DOM
的區(qū)別,找出發(fā)生變化的DOM節(jié)點(diǎn),再利用算法得到相對(duì)更合理的DOM節(jié)點(diǎn)修改方案,最終再將方案應(yīng)用在document
對(duì)象上來改變頁面的展示內(nèi)容。
主流前端SPA框架都離不開【
Virtual-DOM
模型 +DOM-Diff
算法 + 生命周期鉤子】這樣的核心模型。
在上一篇博文《javascript基礎(chǔ)修煉(9)——MVVM中雙向數(shù)據(jù)綁定的基本原理》中,我們通過document.getElementById()
從真實(shí)DOM中獲得了帶有自定義屬性的待解析結(jié)構(gòu),這里是有一些問題的,實(shí)際的過程是先解析模板字符串得到虛擬DOM樹,最后生成真實(shí)的DOM樹。
實(shí)際上我們?cè)谑褂肧PA框架時(shí)所編寫的html
模板,并沒有被直接當(dāng)做DOM片段加載到頁面上使用,而是將文件當(dāng)做字符串讀入到程序中,然后通過解析來生成Virtual-DOM
樹,接著通過SPA框架的渲染函數(shù)來生成必要的片段后才生成真實(shí)的DOM節(jié)點(diǎn)。例如我們要生成下文示例的HTML
片段(為了方便演示,示例中只涉及了類名和文本節(jié)點(diǎn)):
- sidebar-1
- sidebar-2
- sidebar-3
header-zone
core-content
暫未開發(fā)
我們需要構(gòu)建出一個(gè)簡(jiǎn)易模型來表達(dá)上面的結(jié)構(gòu):
virtualDom = {
name:"body",
props:{
className:"main"
},
children:[{
name:"div",
props:{...},
children:[...]
},{
name:"div",
props:{...},
children:[...]
},{
name:"div",
props:{...},
children:[...]
}]
}
建立一個(gè)生成虛擬節(jié)點(diǎn)的輔助函數(shù):
//構(gòu)建DOM節(jié)點(diǎn)的輔助函數(shù)
function h(name, props, children) {
return {
name:name,
props:props,
children:children
}
}
//手動(dòng)生成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'},['暫未開發(fā)'])
]);
通過上面的方法得到的tree
對(duì)象就涵蓋了模板片段中的結(jié)構(gòu)和關(guān)鍵信息。實(shí)際開發(fā)中并不需要像上面一樣手動(dòng)來填寫DOM結(jié)構(gòu),可以將模板字符串掛載到離線DOM節(jié)點(diǎn)上,然后在遞歸解析的同時(shí)來構(gòu)建Virtual-DOM
就可以了。
至此我們完成了模板的編譯,也得到了Virtual-DOM
對(duì)象,但它似乎并沒有什么用處,畢竟我們已經(jīng)完成了對(duì)模板的解析,渲染出頁面沒什么問題,其實(shí)Virtual-DOM
對(duì)于首屏來說并沒有什么特別重要的意義,它的價(jià)值在模型和視圖發(fā)生變化時(shí)才會(huì)體現(xiàn)。上一篇博文的末尾我們已經(jīng)提到了更新視圖時(shí)的效率問題,當(dāng)數(shù)據(jù)模型發(fā)生變化后,我們需要一個(gè)方法來收集所有需要修改的DOM,并為之提供高效的修改方式(你總不能一有變化就把整個(gè)網(wǎng)頁重新渲染,或者讓數(shù)據(jù)模型各自去修改各自綁定的DOM吧)。那么為了能夠收集所有DOM節(jié)點(diǎn)的變化,我們就需要遍歷所有節(jié)點(diǎn)。
對(duì)數(shù)據(jù)結(jié)構(gòu)和算法有一定了解的讀者很容易想到,遍歷解析一個(gè)Virtual-DOM
實(shí)際上就是對(duì)其進(jìn)行先序深度優(yōu)先遍歷(Pre-Order Depth-First-Search),本節(jié)中,我們先預(yù)熱一下,使用這種方式來復(fù)現(xiàn)一下DOM結(jié)構(gòu)。
function dfswalking(tree) {
var _childrenLength;
//執(zhí)行動(dòng)作
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]);
}
}
}
本例中僅打印出字符串的方式來展示,可以在控制臺(tái)看到輸出結(jié)果:
下一篇博文中將分析如何通過domDiff(oldTree, newTree)
的方法通過同樣的遍歷方法來收集變化并批量更新視圖。
本篇只是部分原理的學(xué)習(xí)筆記,并不代表框架真實(shí)源碼的實(shí)現(xiàn)邏輯。