一狡逢、虛擬Dom簡介
虛擬Dom的最初出現(xiàn)是在Rect中逻谦,性能卓越
二刷晋、什么是虛擬Dom?
vdom可以看作是一個使用javascript模擬了DOM結構的樹形結構矢否,這個樹結構包含整個DOM結構的信息仲闽,如以下代碼:
//原始html結構
<ul class="list">
<li class="child">child1</li>
<li class="child">child2</li>
</ul
//虛擬Dom結構
{
tag:"ul",
attrs:{
className:"list"
},
children:[
{
tag:"li",
attrs:{
className:"child"
},
children:["child1"]
}, {
tag:"li",
attrs:{
className:"child"
},
children:["child2"]
}
]
}
可見上方的DOM結構,不論是標簽名稱還是標簽的屬性或標簽的子集僵朗,都會對應在下邊的樹結構里蔼囊。
三、為什么需要虛擬DOM衣迷,它有什么好處?
Web界面由DOM樹(樹的意思是數(shù)據(jù)結構)來構建畏鼓,當其中一部分發(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 本質上就是在 JS 和 DOM 之間做了一個緩存贴膘。可以類比 CPU 和硬盤略号,既然硬盤這么慢刑峡,我們就在它們之間加個緩存:既然 DOM 這么慢,我們就在它們 JS 和 DOM 之間加個緩存玄柠。CPU(JS)只操作內(nèi)存(Virtual DOM)突梦,最后的時候再把變更寫入硬盤(DOM)
(3)提升渲染性能
Virtual DOM的優(yōu)勢不在于單次的操作,而是在大量羽利、頻繁的數(shù)據(jù)更新下宫患,能夠對視圖進行合理、高效的更新这弧。
為了實現(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 對象結構表示 DOM 樹的結構;然后用這個樹構建一個真正的 DOM 樹励堡,插到文檔當中
當狀態(tài)變更的時候谷丸,重新構造一棵新的對象樹。然后用新的樹和舊的樹進行比較应结,記錄兩棵樹差異
把所記錄的差異應用到所構建的真正的DOM樹上刨疼,視圖就更新了
四、diff 算法的實現(xiàn)過程
diff 算法本身非常復雜鹅龄,實現(xiàn)難度很大揩慕。本文去繁就簡,粗略介紹以下兩個核心函數(shù)實現(xiàn)流程:
patch(container,vnode) :初次渲染的時候扮休,將VDOM渲染成真正的DOM然后插入到容器里面迎卤。
patch(vnode,newVnode):再次渲染的時候,將新的vnode和舊的vnode相對比玷坠,然后之間差異應用到所構建的真正的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)
}
}
)}
如果感覺有幫助,請留下一個寶貴的贊或者給小編一個贊賞H茉拧6铡!