一、虛擬Dom簡介
虛擬Dom的最初出現(xiàn)是在Rect中,性能卓越
二、什么是虛擬Dom?
vdom可以看作是一個使用javascript模擬了DOM結(jié)構(gòu)的樹形結(jié)構(gòu),這個樹結(jié)構(gòu)包含整個DOM結(jié)構(gòu)的信息,如以下代碼:
//原始html結(jié)構(gòu)
<ul class="list">
<li class="child">child1</li>
<li class="child">child2</li>
</ul
//虛擬Dom結(jié)構(gòu)
{
tag:"ul",
attrs:{
className:"list"
},
children:[
{
tag:"li",
attrs:{
className:"child"
},
children:["child1"]
}, {
tag:"li",
attrs:{
className:"child"
},
children:["child2"]
}
]
}
可見上方的DOM結(jié)構(gòu),不論是標簽名稱還是標簽的屬性或標簽的子集,都會對應在下邊的樹結(jié)構(gòu)里。
三、為什么需要虛擬DOM,它有什么好處?
Web界面由DOM樹(樹的意思是數(shù)據(jù)結(jié)構(gòu))來構(gòu)建,當其中一部分發(fā)生變化時,其實就是對應某個DOM節(jié)點發(fā)生了變化,
虛擬DOM就是為了解決瀏覽器性能問題而被設計出來的。如前,若一次操作中有10次更新DOM的動作,虛擬DOM不會立即操作DOM,而是將這10次更新的diff內(nèi)容保存到本地一個JS對象中,最終將這個JS對象一次性attch到DOM樹上,再進行后續(xù)操作,避免大量無謂的計算量。所以,用JS對象模擬DOM節(jié)點的好處是,頁面的更新可以先全部反映在JS對象(虛擬DOM)上,操作內(nèi)存中的JS對象的速度顯然要更快,等更新完成后,再將最終的JS對象映射成真實的DOM,交由瀏覽器去繪制。
<1>具備跨平臺的優(yōu)勢
由于 Virtual DOM 是以 JavaScript 對象為基礎而不依賴真實平臺環(huán)境,所以使它具有了跨平臺的能力,比如說瀏覽器平臺、Weex、Node 等。
(2)操作 DOM 慢,js運行效率高。我們可以將DOM對比操作放在JS層,提高效率。
因為DOM操作的執(zhí)行速度遠不如Javascript的運算速度快,因此,把大量的DOM操作搬運到Javascript中,運用patching算法來計算出真正需要更新的節(jié)點,最大限度地減少DOM操作,從而顯著提高性能。
Virtual DOM 本質(zhì)上就是在 JS 和 DOM 之間做了一個緩存。可以類比 CPU 和硬盤,既然硬盤這么慢,我們就在它們之間加個緩存:既然 DOM 這么慢,我們就在它們 JS 和 DOM 之間加個緩存。CPU(JS)只操作內(nèi)存(Virtual DOM),最后的時候再把變更寫入硬盤(DOM)
(3)提升渲染性能
Virtual DOM的優(yōu)勢不在于單次的操作,而是在大量、頻繁的數(shù)據(jù)更新下,能夠?qū)σ晥D進行合理、高效的更新。
為了實現(xiàn)高效的DOM操作,一套高效的虛擬DOM diff算法顯得很有必要。我們通過patch 的核心----diff 算法,找出本次DOM需要更新的節(jié)點來更新,其他的不更新。比如修改某個model 100次,從1加到100,那么有了Virtual DOM的緩存之后,只會把最后一次修改patch到view上。那diff 算法的實現(xiàn)過程是怎樣的?
diff算法

Vue的diff算法是基于snabbdom改造過來的,僅在同級的vnode間做diff,遞歸地進行同級vnode的diff,最終實現(xiàn)整個DOM樹的更新。因為跨層級的操作是非常少的,忽略不計,這樣時間復雜度就從O(n3)變成O(n)。
diff 算法包括幾個步驟:
用 JavaScript 對象結(jié)構(gòu)表示 DOM 樹的結(jié)構(gòu);然后用這個樹構(gòu)建一個真正的 DOM 樹,插到文檔當中
當狀態(tài)變更的時候,重新構(gòu)造一棵新的對象樹。然后用新的樹和舊的樹進行比較,記錄兩棵樹差異
把所記錄的差異應用到所構(gòu)建的真正的DOM樹上,視圖就更新了
四、diff 算法的實現(xiàn)過程
diff 算法本身非常復雜,實現(xiàn)難度很大。本文去繁就簡,粗略介紹以下兩個核心函數(shù)實現(xiàn)流程:
patch(container,vnode) :初次渲染的時候,將VDOM渲染成真正的DOM然后插入到容器里面。
patch(vnode,newVnode):再次渲染的時候,將新的vnode和舊的vnode相對比,然后之間差異應用到所構(gòu)建的真正的DOM樹上。
- patch(container,vnode)
通過這個函數(shù)可以讓VNode渲染成真正的DOM,我們通過以下模擬代碼,可以了解大致過程:
function createElement(vnode) {
var tag = vnode.tag
var attrs = vnode.attrs || {}
var children = vnode.children || []
if (!tag) {
return null
}
// 創(chuàng)建真實的 DOM 元素
var elem = document.createElement(tag)
// 屬性
var attrName
for (attrName in attrs) {
if (attrs.hasOwnProperty(attrName)) {
// 給 elem 添加屬性
elem.setAttribute(attrName, attrs[attrName])
}
}
// 子元素
children.forEach(function (childVnode) {
// 給 elem 添加子元素,如果還有子節(jié)點,則遞歸的生成子節(jié)點。
elem.appendChild(createElement(childVnode)) // 遞歸
}) // 返回真實的 DOM 元素
return elem
}
- patch(vnode,newVnode)
//考慮新舊節(jié)點對比的情況
function updateChildren(vnode, newVnode) {
var children = vnode.children || []
var newChildren = newVnode.children || []
// 遍歷現(xiàn)有的children
children.forEach(function (childVnode, index) {
var newChildVnode = newChildren[index]
// 兩者tag一樣
if (childVnode.tag === newChildVnode.tag) {
// 深層次對比,遞歸
updateChildren(childVnode, newChildVnode)
} else {
// 兩者tag不一樣
replaceNode(childVnode, newChildVnode)
}
}
)}
如果感覺有幫助,請留下一個寶貴的贊或者給小編一個贊賞?。?!