在分析之前,先將例子進(jìn)行小小的改動旬迹,改動后的文件如下
? ? ? ? ? ? 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的過程其實(shí)就是遞歸調(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)ⅲ虼舜a定位到src\core\instance\lifecycle.js下的
進(jìn)入update洼哎,該函數(shù)的入?yún)榻M件Vnode和false烫映,變量緩存的值如下
? ? ? ? ? ? a-vm即Vue沼本,因?yàn)閠his在es5中指向的是其調(diào)用者
? ? ? ? ? ? b-prevEl:如果當(dāng)前組件是子組件的話,那么其指向的父組件所對應(yīng)的dom锭沟,因?yàn)樵诖嬖诟附M件的時候抽兆,父組件的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實(shí)際上是父子關(guān)系,因此prevActiveInstance=vue悼沿,activeInstance=vm等舔,并返回一個函數(shù)
? ? ? ? ? ? e-vm._vnode=當(dāng)前的組件Vnode
代碼向下
由于prevVnode=null,因此走進(jìn)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劫笙,進(jìn)入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進(jìn)行構(gòu)建并生成了一個組件對象蠕搜,該組件對象上掛載了data.hook
組件的vnode如下
因此i的值如下
? ? ? ? a-isDef(i)值為true怎茫,進(jìn)入判斷
? ? ? ? b-i?=?i.hook為true,i.hook.init為true妓灌,進(jìn)入判斷執(zhí)行init方法轨蛤;該方法在組件vnode化的過程中,在createComponent中調(diào)用installComponentHooks被添加到組件的hook上虫埂,因此調(diào)用的實(shí)際上是
? ? 該方法的入?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é)分析過,它實(shí)際上是定義了一個Sub構(gòu)造函數(shù)斧散,并通過原型鏈繼承了vue的方法供常,因此new?vnode.componentOptions.Ctor調(diào)用的實(shí)際是
這里的this指向的是vue,因此調(diào)用的實(shí)際上是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瞻鹏,進(jìn)入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痪蝇;進(jìn)入判斷,向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方法中再一次實(shí)例化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é)點(diǎn)時拇泛,將執(zhí)行掛載。因此定位代碼至createElm函數(shù)中
createComponent返回undefined思灌,表示不存在更多的子組件俺叭,代碼向下,調(diào)用createChildren遞歸調(diào)用createElm一個一個生成dom節(jié)點(diǎn)泰偿,最后執(zhí)行insert插入到dom文檔當(dāng)中