原文鏈接我的blog,歡迎STAR。
這次和想要大家分享的一篇文章解析Vue diff 算法
在上篇里,我們提到在渲染時,render>template>el,但是最終,我們得到的都是render函數(shù),那么render函數(shù)的作用是什么?接下來該干什么?
帶著兩個問題,我們深入源碼。
首先來解決第一個問題:
-
render函數(shù)的作用是什么?在/src/core/instance/lifecycle.js中有這么一段代碼:
vm._watch = new Watch(vm, updateComponent, noop)
然后找到updateComponent
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
意思是,通過Watcher的綁定,當(dāng)數(shù)據(jù)發(fā)生變化時,會執(zhí)行updateComponent方法,根據(jù)不同的條件,調(diào)用的updateComponent也不同,在開發(fā)環(huán)境下,主要調(diào)用的是else
里的updateComponent方法,意思是在執(zhí)行vm._update方法之前會先執(zhí)行vm._render()方法。
接著找到vm._render()方法,在src/core/instance/render.js文件里,有這么一段代碼:
在vm._render方法里,會得到一個VNode對象。至此,第一個問題解決,但是VNode, 又是什么?將在解決第二個問題時,解決。
- 在已經(jīng)知曉
render函數(shù)會得到VNode對象,那么VNode又是什么,下一步又將做什么?
關(guān)于VNode, 其實就是Vue.js 2.0 的 Virtual DOM對象,
在/src/core/vdom/vnode.js中,可以看到
VNode具體的一些數(shù)據(jù):
這些數(shù)據(jù),既是對DOM節(jié)點的一些描述。
如果你有興趣,可以做個實驗,瞧瞧真實DOM對象,
const div = document.createElement('div')
for (let k in div) {
console.log(k)
}
倘若每次都生成一個DOM,對性能該是多大的浪費。
于是我們總結(jié)出VNode其實Virtual DOM對象,就是用一個簡單一點的對象去代替復(fù)雜的dom對象。生成VNode,Virtual DOM對象之后,接下來,需要通過DOM Diff,生成真正的DOM節(jié)點。
關(guān)于DOM diff 說簡單點,其實就是所有的變動先發(fā)生在Virtual DOM對象上,然后再將實際發(fā)生改變的部分反應(yīng)到真實DOM上。
值得注意的一點是,比較只會在同級進(jìn)行,并不會跨層級比較
引用來自React’s diff algorithm的一張圖:
分析源碼,在/src/core/vdom/patch.js的patch方法中:
function patch(oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {
// ...
}
patch函數(shù)接收六個參數(shù):
oldVnode: 舊的Virtual DOM或者舊的真實DOM;
vnode:新的Virtual DOM;
hydrating : 是否與真實DOM混合;
removeOnly: 這個在源碼里有提到,removeOnly is a special flag used only by <transition-group>也就是說是特殊的flag,用于transition-group組件;
parentElm: 父節(jié)點;
refElm: 新節(jié)點插入到refElm之前。
分析具體實現(xiàn)思路:
- 如果
vnode不存在,oldVnode存在,此時需要銷毀oldVnode
if (isUndef(vnode)) {
if (isDef(oldVnode)) invokeDestoryHook(oldVnode)
return
}
invokeDestroyHook()方法也就是來銷毀節(jié)點的一個方法。
- 如果
oldVnode不存在,vnode存在,需要調(diào)用createElm()方法創(chuàng)建新節(jié)點
if (isUndef(oldVnode)) {
isInitialpatch = true
createElm(vnode, inserteVnodeQueue, parentElm, refElm)
}
isInitialpatch = true 主要用來作延遲插入處理。
-
如果
vnode與oldVnode都存在:- 如果
oldVnode不為真實節(jié)點,且oldVnode與vnode是同一節(jié)點,那么調(diào)用patchVnode()方法,(patchVnode稍后詳細(xì)講解)。
const isRealElement = isDef(oldVnode.nodeType) if (!isRealElement && sameVnode(old, vnode)) { patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly) }- 如果不為同一節(jié)點時,當(dāng)
oldVnode是真實節(jié)點,并且是元素節(jié)點,且含有server-render屬性的時候,移除server-render屬性,把hydrating設(shè)置為true,并調(diào)用hydrate函數(shù),將Virtual DOM與真實DOM之間進(jìn)行映射,然后將oldVnode設(shè)置為對應(yīng)的Virtual DOM,找到oldVnode.elm的父節(jié)點,根據(jù)vnode創(chuàng)建一個真實DOM節(jié)點,并插入到該父節(jié)點的oldVnode.elm的位置。
- 如果
最后返回
vnode.elm, 那么和最開始進(jìn)如的vnode.el 有什么區(qū)別在哪?
其實很簡單,因為最開始作為參數(shù)傳進(jìn)去的是新的Virtual DOM對象,也就是說不是真實DOM,再具體點,就是 vnode.elm = null. 經(jīng)過函數(shù)返回以后,現(xiàn)在引用的是對應(yīng)的真實dom。
至此,完成一個patch過程。
下篇里,將從源碼解析patchVnode()以及updateChildren()方法。
完。