首先浏覽器加載一個HTML頁面時會經過建立DOM 樹、建立樣式規則(style rules)、建構渲染樹(render tree)、布局layout 和 繪制頁面(painting)。
傳統的原生api或jQuery去操作DOM的時候,浏覽器會從建構DOM樹開始從頭到尾執行一遍流程,是以頻繁的操作DOM的代價是昂貴的。還會出現頁面卡頓,影響使用者的體驗。
虛拟DOM就是為了解決這個問題而被設計出來,加入一次操作中有10次操作的動作,虛拟DOM不會立即執行這個操作,而是将這10次更新的diff内容
虛拟DOM是在DOM的基礎上在記憶體建立了一個抽象層,對資料和狀态所做的任何改動,都會被自動且高效的同步到虛拟DOM,最後再批量同步到DOM中
render執行的結果得到的并不是真正的DOM節點,而僅僅是javascript對象,稱之為虛拟DOM
虛拟DOM具有批處理和高效的Diff算法,可以無需擔心性能問題而随時‘重新整理’整個頁面,因為虛拟DOM能保證對界面上真正變化的部分進行實際的DOM操作
【傳統DOM操作(eg:innerHtml)】:render html+重建所有DOM元素
【虛拟DOM】:render 虛拟DOM + diff算法+更新必要的DOM元素
Vue.js将DOM抽象成一個以JavaScript對象為節點的虛拟DOM樹,以VNode節點模拟真實DOM,可以對這顆抽象樹進行建立節點、删除節點以及修改節點等操作,在這過程中都不需要操作真實DOM,隻需要操作JavaScript對象,大大提升了性能。修改以後經過diff算法得出一些需要修改的最小機關,再将這些小機關的視圖進行更新。這樣做減少了很多不需要的DOM操作,大大提高了性能。
diff的過程就是調用patch函數,就像打更新檔一樣修改真實dom。
function patch (oldVnode, vnode) {
if (sameVnode(oldVnode, vnode)) {
patchVnode(oldVnode, vnode)
} else {
const oEl = oldVnode.el
let parentEle = api.parentNode(oEl)
createEle(vnode)
if (parentEle !== null) {
api.insertBefore(parentEle, vnode.el, api.nextSibling(oEl))
api.removeChild(parentEle, oldVnode.el)
oldVnode = null
}
}
return vnode
}
patch
函數有兩個參數,
vnode
和
oldVnode
,也就是新舊兩個虛拟節點。在這之前,我們先了解完整的vnode都有什麼屬性,舉個一個簡單的例子:
// body下的 <div id="v" class="classA"><div> 對應的 oldVnode 就是
{
el: div //對真實的節點的引用,本例中就是document.querySelector('#id.classA')
tagName: 'DIV', //節點的标簽
sel: 'div#v.classA' //節點的選擇器
data: null, // 一個存儲節點屬性的對象,對應節點的el[prop]屬性,例如onclick , style
children: [], //存儲子節點的數組,每個子節點也是vnode結構
text: null, //如果是文本節點,對應文本節點的textContent,否則為null
}
需要注意的是,el屬性引用的是此 virtual dom對應的真實dom,
patch
的
vnode
參數的
el
最初是null,因為
patch
之前它還沒有對應的真實dom。