在分析之前,先將例子進行小小的改動,改動后的文件如下
? ? ? ? ? ? main.js文件
? ? ? ? app.vue文件
? ? ? ? 在components文件下新增child.vue,并在app.vue中引入
通過之前幾篇的分析我們知道,import vue執(zhí)行初始化構(gòu)建了vue類,new vue調(diào)用init方法判斷el存在執(zhí)行mount,在mount過程中調(diào)用mountComponent并在該函數(shù)中定義渲染watcher,而在渲染watcher內(nèi)又調(diào)用了updateComponent函數(shù),該函數(shù)首先拿到vue的渲染vnode,后調(diào)用update執(zhí)行patch,patch的過程其實就是遞歸調(diào)用原生dom方法去create和insert,而在createElement過程中將根據(jù)類型生成不同的vnode,也就是在new vue時指定的'el:#app'調(diào)用的是create-element,在子組件app patch時調(diào)用的create-component。通過上一節(jié)(組件的vnode)分析我們拿到了組件的Vnode,它將作為_render函數(shù)的返回值,也就是_update的入?yún)?,因此代碼定位到src\core\instance\lifecycle.js下的
進入update,該函數(shù)的入?yún)榻M件Vnode和false,變量緩存的值如下
? ? ? ? ? ? a-vm即Vue,因為this在es5中指向的是其調(diào)用者
? ? ? ? ? ? b-prevEl:如果當(dāng)前組件是子組件的話,那么其指向的父組件所對應(yīng)的dom,因為在存在父組件的時候,父組件的patch過程的返回值將被vm.$el緩存,而vm.$el同時又作為patch的參數(shù)傳入,也就是說,在patch之前vm.$el是有值的,換句話說,在update之前是有值的。因此我們向前查找mount過程,在mountComponent中向vm.$el掛載了值
而el則是在調(diào)用時傳入的,因此繼續(xù)向上查找,找到$mount
這里的el又是從上一級傳入的,因此繼續(xù)向前查找到init方法
我們發(fā)現(xiàn),在調(diào)用mount方法時傳入了el參數(shù),而在app.vue中并未指定,因此為undefined,即prevEl=undefined
? ??????????c-prevVnode取得是vm._vnode,而vm._vnode在后邊保存的是vnode,因此我們需要找到new vue過程中的vnode是什么,我們發(fā)現(xiàn)在組件init的過程中調(diào)用了
而在initRender中又執(zhí)行了
因此prevVnode=null
? ??????????d-restoreActiveInstance調(diào)用setActiveInstance
該函數(shù)首先保存了上一次的instance,而上一次是vue,接著又向activeInstance緩存了一份當(dāng)前組件的vm,因此,prevActiveInstance 和 activeInstance實際上是父子關(guān)系,因此prevActiveInstance=vue,activeInstance=vm,并返回一個函數(shù)
? ? ? ? ? ? e-vm._vnode=當(dāng)前的組件Vnode
代碼向下
由于prevVnode=null,因此走進if判斷,執(zhí)行patch方法,該方法入?yún)?/p>
(undefined,'組件Vnode',false,false)
查找patch方法,該方法在src\platforms\web\runtime\index.js文件中被掛載到vue原型,根據(jù)import引入路徑查找到patch
? ? a-isUndef(vnode)=false,跳過
? ? b-isUndef(oldVnode),由于oldVnode是undefined,故為true,進入if判斷,調(diào)用createElm,該方法入?yún)?/p>
(‘組件vnode’,[])
? ? ? ? a-vnode.elm=undefined,跳過
? ? ? ? b-調(diào)用createComponent方法,該方法入?yún)?/p>
('組件vnode',[],undefined,undefined)
從代碼可以看出,該函數(shù)會返回兩個值,一個是true,一個是undefined。從注釋可以看出,返回true的前提是執(zhí)行了i,也就是i=i.hook,因此核心是vnode.data是什么
我們在上一節(jié)(組件的vnode化)分析中得知,在構(gòu)建vue的vnode過程中,對app.vue進行構(gòu)建并生成了一個組件對象,該組件對象上掛載了data.hook
組件的vnode如下
因此i的值如下
? ? ? ? a-isDef(i)值為true,進入判斷
? ? ? ? b-i?=?i.hook為true,i.hook.init為true,進入判斷執(zhí)行init方法;該方法在組件vnode化的過程中,在createComponent中調(diào)用installComponentHooks被添加到組件的hook上,因此調(diào)用的實際上是
? ? 該方法的入?yún)?/p>
('組件vnode',false)
a-vnode.data.keepAlive=false,代碼走向else,調(diào)用createComponentInstanceForVnode方法
該方法入?yún)?/p>
('組件vnode',vm)
a-inlineTemplate為false,跳過
b-new?vnode.componentOptions.Ctor:打開vnode類定義
componentOptions是在生成vnode時傳入的參數(shù),對應(yīng)的參數(shù)即在生成vnode時傳遞的如下
而Ctor則是調(diào)用vue.extend的返回值,我們在上一節(jié)分析過,它實際上是定義了一個Sub構(gòu)造函數(shù),并通過原型鏈繼承了vue的方法,因此new?vnode.componentOptions.Ctor調(diào)用的實際是
這里的this指向的是vue,因此調(diào)用的實際上是vue.init方法
該方法的入?yún)閚ew?vnode.componentOptions.Ctor時傳遞的object
代碼向下
? ? ? ? a-vm指向vue
? ? ? ? b-vm._uid;在new vue的時候也調(diào)用了init方法,因此這里至少一級遞增一次,由于++在后是先使用后遞增,故為1
? ? ? ? c-options?&&?options._isComponent=true,進入if判斷,調(diào)用initInternalComponent方法
該方法的入?yún)?/p>
(vue,'new?vnode.componentOptions.Ctor時傳遞的object')
? ? ? ? i-opts;Object.create方法將創(chuàng)建一個新對象,并將該對象的__proto__指向參數(shù)對象
? ? ? ? ii-parentVnode;取自options._parentVnode,該鍵保存著vnode的引用
? ? ? ? iii-opts.parent是vue;當(dāng)我們調(diào)用data.hook.init時傳遞的是app.vue的組件vnode,在該init方法下調(diào)用了createComponentInstanceForVnode,傳遞了當(dāng)前的組件vnode和activeInstance,并將activeInstance作為parent合并到options上
因此,查找activeInstance的值,該值在update過程中調(diào)用setActiveInstance設(shè)置為vue,是一個全局的值
????????iv-opts._parentVnode='組件vnode'
回到組件app.vue過程的init方法,調(diào)用initLifecycle
? ? ? ? ? ? a-parent?&&?!options.abstract=true;進入判斷,向options掛載$children,成員為當(dāng)前組件vnode
? ? ? ? ? ? b-vm.$parent=vue;故組件的$parent指向vue,vue的$children指向vnode,兩者是父子關(guān)系
回到組件的init過程,el不存在,init方法結(jié)束,回到組件的hook的init方法中,調(diào)用child.$mount方法
? ?拿到的el為undefined,調(diào)用mountComponent,在mountComponent方法中再一次實例化Watcher,在Watcher中又再一次update和render,此時的render構(gòu)建的是app.vue內(nèi)部的元素,拿到的vm就是app.vue的組件對象,該對象中使用parent指向vue,$options中存在parent指向vue,_parentVnode代表在vue組件對象中的占位符,然后render之后再一次update走patch過程,由于app.vue中存在組件child,因此又會生成子組件的vnode,并調(diào)用子組件的init-mount-render-update,依次類推。當(dāng)最后一次嵌套的組件為普通節(jié)點時,將執(zhí)行掛載。因此定位代碼至createElm函數(shù)中
createComponent返回undefined,表示不存在更多的子組件,代碼向下,調(diào)用createChildren遞歸調(diào)用createElm一個一個生成dom節(jié)點,最后執(zhí)行insert插入到dom文檔當(dāng)中