關(guān)于一些Vue的文章。(3)

原文鏈接我的blog,歡迎STAR。

這次和想要大家分享的一篇文章解析Vue diff 算法

在上篇里,我們提到在渲染時,render>template>el,但是最終,我們得到的都是render函數(shù),那么render函數(shù)的作用是什么?接下來該干什么?

帶著兩個問題,我們深入源碼。


首先來解決第一個問題:

 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 主要用來作延遲插入處理。

  • 如果vnodeoldVnode 都存在:

    • 如果oldVnode不為真實節(jié)點,且 oldVnodevnode是同一節(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()方法。

完。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容