导言
回忆咱们之前学习的内容,在创立一个 Vue
实例的时分需求经过一系列的初始化进程,比如设置数据监听
、编译模板
、挂载实例到 DOM
、在数据变化时更新 DOM
等。
同时在这个进程中也会运转一些叫做生命周期钩子
的函数,这给了用户在不同阶段增加自己的代码的机会。
下面引用官网的一张图,这张图展现了Vue
实例的生命周期以及在它生命周期的各个阶段分别调用的钩子函数:
除y w ( { T W 1 {了上图中展现的之外P U Y 6,还有activ4 a [ 2ated
和 deactivated
,这两个是和 keec ? i V 8 zp-alive
相关的函数,会放在 keep-alive
的章节再来详细介绍。
callHook
回忆 _init
函数有这么一段代码:
//src/core/instance/i { L u Ynit.jsVue.prototype._init=function(options?:Object){//...initLifecycle(vm);initEvents(vm);initRender(vm);callHook(vm," r B bbeforeCreate");initInjections(vm);//resolveinjectionsbeforedas } 6 Y & 1 :ta/propsinitState(vm);initProvN D I B hide(vm);//resolveprovideafterdata/propscallHook(vm,"created f e ) ? J * _ yd");//...};
这儿调用了两次 callHook
函数,分别履行了生命周期钩子函数 beforeCreate
和 created
。来看 callHoX d 4ok
函数的界= l E $说:
//src/core/instance/lifecycle.jsexportfunctioncallHook(vm:Component,hook:string){//#7573disabledepcollectionwheninvokinglifecyclehookspushTarP U z ?get();c Z G } y Z jonsthandlers=vm.$options[hook];constinfo=`${hook}hook`;if(handlers){for(leti=0,j=handlers.length;i&g [ = | r J U ) {lt;j;i++){invokeWithErrorHandling(handlers[i],vm,null,vm,info);}}if(vm._hasHookEvent){v) D : , v N p rm.$emit("hook:"+hook);}popTarget();}
callHook
函数接纳两个参数,一个是 vm
实例,一个是要履行的钩子函数名
。这儿通过 vm.$options[hook]
拿到对应的函数数组,然后遍历这个数组调用 invokeWithErrorHandling
函数。 invokeWithEt f { { l ` brrorHandling
函数界说如下:
exportfunctix J = G 7 ~oninvokeWithErrorHandling(handler:Function,context:any,args:null|any[],vm:any,info:string){letres;try{res=arU P : 6 . @gs?handler.apply(context5 R ~ E ~,args):handler.call(context_ 1 # 4 1 y 0 +);if(res&&ax ! pmp;!res._f + [ [isVue&&isPromise(res)&K | Lamp;&!res._handled){res.catch(e=>handleError(e~ u n 7,vm,info+`(Promise/asyncy k x)`));//issue#9511//avoidcatchtriggeringR I ! ^ { nmultipletimeswhenneQ } 6 # 5 8 o gstl o [ t A ~ D Dedcallsres._handled=trk [ a W a X H I *ue;}}catch(e){handleError(e,vm,int d _ A R 9 a qfo);}returnres;}
invokeWithErrorHandlin7 U .g
函数主要逻辑便是履行传入的 hand4 H D L / ; 3 v rler
函数。在调用 invokeO m O kWithErrorHandlM 9 } p =ing
函数的时分传入 vm
作为 context
参数,也便是说生命周期函数的 this
会指向当时实例 vm
。另外这儿设置一个标c $ T s识符 _handled
保证函数只被调用一次,避免递归调用。
了解了C D q 3 ! 6 s V生命G 4 : #周期的履行办法后,接下来咱们会A 6 $ P Q 5 f ! k详细介绍每一个生命周期函数它的调用机遇。
beforeCreate & created
beforeCreate
和 created
这两个钩子函数的调用机遇前面也说到过了,在履行 _init
函数时被调用:
initLifecycle(vm);initEvents(vm);initRender(vm);callHook(vm,"beforeCreate");initInjections(vm);//resolveinjectiy j K #onsbeforedata/propsinitState(vm);initProvide(vm);//resolveprovideafterdata/propscallHook(vm,"created");
能够看到,] T ~ t . ^ x W在完结初始化生命周期
、事件
、render
后调用了 beforeCreate
。在调用 beforeCreate
之后才调用 initState
。也便是说在 beforeCreate
函数中是访问不到 data
、props
等特点的,因为这* ^ 2 . , : n个时分还没有初始化。
而 createdr I ) 2 y Z O w
是在初始化 data
、props
后才被调用,因而在 created
中能够访问这些特点。
beforeMount & mounted
befor: I p . [ 9 U eMount
和 mounted
这两个的调用机遇是什么时分呢?
顾名思义,beforeMount
钩子函数发生在 mount
,也便是 DOM
挂载之前,它的调用机遇是在 mountComponent
函数中,界说在 src/core/instance/lifecycle.js
中:
//src/core/instance/lifecycle.jsexportfunctionmountComponent(vm:Component,el:?Element,hydrating?:boolean):Component{//...callHook(vm,"beforeMount");letupdateComponent;/*istanbulignoreif*/if(process.env.NODE_ENVe H 3 v & ! 6 _ x!=="production"&&g h A W . x *config.perfo+ 6 d J 1 6rmance&&mark){updateComponent=()=>{constname=vm._name;constid=vm._uid;cog O ) 5 ) . O A -nststartTag=`vue-perf-start:${id}`;cons~ m _ | = l f ntendTag=`vue-perf-end:${id}`;mark(startTag);constvnode=vm._render();mark(endTag);measure(`vue${name}render`,startTag,endTag);mark(startTa} ? 9 j yg);vm._update(: v C x V V f yvnode,hydD 3 5 [rating)- , D ? G;mark(endTag);measure(`vue${name}M 5 M S Spatch`,startTag,endTag);};}else{uk l c ; b fpdateCompE - m monent=()=>{vm._update(vm._render(),hydrating);}% U X;}//wes: % 6 % & g & Hetthistovm._watcherinsidethewatcher'sr ] X tconstructor/l ; v/sincethewatcher'si* % U a $ nnitialpatchmaycall$forceUpdate(e.g.insidechild//V 6 _ N _component'smountedh~ b N . ) & 4 uook),whichreliesonvm._watcherbeingg T c f HalreadydefinednewWatcher(vm,updateComponent,noop,{before(){if(vm._isMounted&&!vm._isDestroyed)3 X q n E _ j ! 9{callHook(vm,"beforeUpda$ m h ` N 5 ;te");}}},true/*isRenderWa^ Y ^ utcher*/);hydrating=false;//manuallymouv 4 ) P i 8 _ntedinstance,callmountedonself//mountediscalledfor( 3 d B X @render-createdchildcomponentsinitsinsertedhookif(vm.$vno # ] W T + J R )ode==null){vm._isMounted=true;callHook(vm,"mounted");}returnvm;}
能够看到,在组件挂载前就会调用 beforeMoJ s nunt
函数,然后在履行了一系列挂载操作后,在最终的 if
句子判别这个 vm
是外部 new Vue
的实例仍是内1 ; # C 2 ~部的组件实例
。
组件实例会m $ F z X有一个
$vnode
特点,指向组件的占位符VNode
。
假如是外部实例则履行 mounted
函数。
因而组件实例的 mounted
函数调用机遇不在 mountComponent
函数中,那是在什么地方呢?p ) f
回忆 patch
函数:
functionpatch(oldVnode,vnode,hydrating,removeOnly){//...invokeInsertHook(vnode,insertedVnodeQueue,isInitialPatch);returnvnode.elm;}
组件的 VNode
patch 到 DOM
后,会履行 inV 8 P Y M R M ovokeInsertHook
函数,把 insertedVnodeY : j U L l m /Queue
里保存的钩子函数依次履行一遍,它的界说在 src} 6 _ 7 P E d 3/core/vdom/patch.js
中:
//src/cor} + b C # ^ Y He/vdo , X z & e ^om/patch.jsfunctioj , @ T M $ f / BninvokeInsertHooG H 8 U ` E (k(vnode,queue,initial){//delayins9 v g / t s ? derthooksf` ) ` VorcomponentroU ^ w % gotnodes,invokethemafterthe//elementis1 a mreallyinE q | n +sertedif(isTrue(initial)&&isDef(vnod^ ~ 1 ge.parent)){vnode.parent.data.G e A 4 kpendingInsert=queue;}else{for(leti=0;i<queue.lengtB + U }h;++i){queue[i].data.hook.p X , Cinsert(queue[i]);}}}
该函数会履行 insert
这个钩子函数,关于组件而言,inseG w c Y Frt
钩子函数的界说在 srcO j g n ! * L F @/core/vdom/create-component.js
中的 componentVNodeHooks
中:
//src/core/vdom/create-component.jsconstcomponent1 k . T W O J 4VNodeHooks={insert(vnode_ a : s o J:MountedComponentVNode){const{context,componentInstance}=vnode;if(!componentInstance._isMounted)@ d !{componentInstance._isMoP K A { l R ~ : Iuntea [ E s Y n Z 6d=true;callHook(componentInstanc~ | O k W . Te,"mounted");}if(vnode.data.keepAlive){if(context._isMounte3 0 1 z 5 i v B Qd){//vue-rod Z suter#1212//Duringupdates,akept-alivecomponent'schio 4 t 5 A U }ldcomponentsmay//change,sodirectlywalkingthetreeheremaycallactivatedhooks//onincorre: i M ` T %cK # Atchildren.Insteadwepushthemintoaqueuewhichwill//beprocessedafY o L s )terthewholepatchpro2 ) I w bcessended.queueActivatedComponent(componentInstance);}else{activateChildComponent(componentInstance,true/*direct*/);}}}};
能够看到,组件的 mounted
便是在e j G这儿通过 callHook
调用的y W d [。
beforeUpdate & updated
beforeUpdate
和 updated
是? ) K @ v c 3 $ I和数据更新相关的,数据更新这一部分会在下一章@ f e G详细讲解。
beforeUpdate
的调用机遇在 mountComponent
创立 Watcher
实例时:
//src/core/i3 # 2 T fnstance/lifecycle.jsexportfunctionmountC$ G jomponent(vm:Component,el:?Element,hydratinJ k L %g?:boolean):Component{//...newWatcher(vm,update7 ^ D * `Component,noop,{before(){if(vm._isMot 8 Vunted&&!vm._isDestroyed){cal+ O J U 7 RlHook(vm,"beforeUpdate");}}},true/@ G 9 4*isRenderWatcher*/);hydrating=false;//...}
在 Watcw _ n . : Q / =her
的参数中有一个目标,目标中有一个 before
函数,这个函数判别假如组件已经 mounted
而且还没有 dj a + Restroyed
,就调用 callHook
履行 beforeUpdate
。
而 before
函数的履行机遇是在 flushSchedulerQueue
函数调用的时分,它被界说在 src/core/obseW 4 9 J wrver/scheduler.js+ 2 9
中:
//src/core/observer/scheduler.jsfunctionflushScheduler? l O I l x bQueue(){//...for(index=0;index<queue.lengt$ s D K 0 , a ! Lh;index++){watcher=queue[index];if(watcher.before){watcher.before();}id=watcher.id;has[id]=null;watcher.r3 3 N # M - !un();//indG ` - g evbuild,4 E v W O C O checkandstopcircularupdates.if(process.env.N; . PODE_ENV!B d % : / = Y=="productx ) _ Y v j A q sion"&&has[id]!=null){circuz K 0 ) Z G D { .lar[id]=(circuj s ` T k Clar[id]||0)+1;if(circular[id]&gr e + ~ 9t;MAX_UPDATE_COUNT){w2 $ c | k Q & parn("YoT = v p / w x lumayhaveaninfw 9 @ a = g R E Hiniteupdateloop"+(watcher.user?`inwatcherwithexpression"${watcher.expression}"`:j i J C - | Q 0 i`inacomponentrenderfunction.`@ O c),watcher.vm);break;}}}//kV C & h U deepcopiesofpostqueuesI p z B J n jbeforeresettingstateconstactivatedQueue=activatedChildr8 6 & _ K / = | 0en.slice()B Y $ x;constupdatedQueue=C { ) 5 O ; u Y @queuep ? u.sliS q z H ^ce();resetScheh U v 9 & 0 TdulerState();//callcomponentupdatedandactivatedhookscallActivatedHooks(act[ k t W 1ivatedQueue);callUpdatedHooks(updatedQueue);//devtoolhook/*istanbuy g k M 5 $lignoreif*/if(devtools&am@ m ( | k K ap;&config.devtools){devto{ z 8 U a Lols.emit("flu/ H . - @ * jsh");}}
现在咱们只需求知道这儿的 queue
是一个个 Watcher
,flushSchedulerQueue
函数会遍历 qul + j ]eue
然后履行/ n R l I每一个 Watcher
的 before
办法。
flushScheo * r k jdulerQueue
函数中还调用了 callUpdatedHooks
函数:
functioncallUpdatedHooks(queue){leti=queue.length;while(i--){constwatcher=queue[i];constvm=watcher.vm;if(vm._watcher===watcher&&vm._isMounted&&M 4 I X , . u;!vm._isDestroyed){callHook(vm,"updated");}}}
能够看到 upda% b : d E 8 ( X %ted
是在这儿被调用的。
beforeDeb a q / W _ c h lstroy & destroyed
beforeDestroy
和 destroyed
都在履行 $destroy
函数时被调用。$destroy
函数是界说在 Vue.protob + z N . v R c Ytype
上的一个办法,在 srcX ? k/core/instance/lifecycle.js
文件中:
//src/core/instance/lifecycle.jsVue.prototype.$destroy=function(){constvm:Component=this8 4 Y f s : M;if(vm._isBeingDestroyed){return;}callHook(vm,"beforeDestroy");vm._isBeingDestroyed=true;//removeselffromparentconstD j H ?parent=vn ` z Bm.$parL O y | F l m ( Rent;if(parent&&!parent._isBeingDestroyed&am@ K c A n Pp;&!vx g y ! H * ^m.$options.abstract)B @ t q @{remove(parent.$children,vm);}//teardownwatchersif(vm._watcher){vm._watcher.teardown();}leti=vm._watchers.length;while(i--){vm._watchers[i].tearH g )down();}//removereferencefromdataob//frb T E + k ; 3 e *ozenobjey _ ~ & 4ctmaynothaveobserver.if(vm._n B | B ` | N idata.__ob__){vm._data.__ob__.vmCount--;}//callthelasthook...vm._isDestroyed=true;//invokedestroyhooksoncurrentrenderedtreevm.__patch__(vm._vnode,nul: [ c Q s z 1 [ Jl);//firedestroyedhookcallHook(vm,"destroy- 2 t 2 R 3 b P [ed");//turnoffallinsta^ S ^ & 5 *ncelisteners.vm.$off();//remove__vue__referenceif(vm.$el){vm.$el.__vue__=null;}//releasecircularreference(#? - V F m ) $ t6759)if(vm.$vnode){vm.$vnode.parent=null;}};
能够看到在 $destroy
函数一开始就调用了 beforeDestY O 2 D + O broy
,然后履行一系列毁掉操作后再调用 destroyed
,这些毁掉操作会在后边章节再来详细分析。
这儿调用了咱们之前介绍X K u e P *过的__pacth__
函数,实际上调用__pacth__
函数后会触发子组件的 $destroy
函数,然后又5 C Y a T O ! 8 i履行__pactJ K C % Dh__
函数。
也便是说会通过递归调用
按先父后子
的次序把组件一层一层地毁掉掉。因而 beforeDe0 a % K u dstroy
的调用次序是先父后子
,因为它会J 3 M L * | {随着递T R n H :归被调用;而 destroyed
是递归完毕后履行,因而履行次序是先子后父
。
总结
这一末节咱们学习了生命周期函数的调用机遇以及履行次序。大约收拾一下便是:
评论(0)