导言
回忆咱们之前学习的内容,在创立一个 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.js
Vue.prototype._init=function(options?:Object){
//...
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm," r B bbeforeCreate");
initInjections(vm);//resolveinjectionsbeforedas } 6 Y & 1 :ta/props
initState(vm);
initProvN D I B hide(vm);//resolveprovideafterdata/props
callHook(vm,"created f e ) ? J * _ yd");
//...
};
这儿调用了两次 callHook
函数,分别履行了生命周期钩子函数 beforeCreate
和 created
。来看 callHoX d 4ok
函数的界= l E $说:
//src/core/instance/lifecycle.js
exportfunctioncallHook(vm:Component,hook:string){
//#7573disabledepcollectionwheninvokinglifecyclehooks
pushTarP 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 Dedcalls
res._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/props
initState(vm);
initProvide(vm);//resolveprovideafterdata/props
callHook(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.js
exportfunctionmountComponent(
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 Halreadydefined
newWatcher(
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-createdchildcomponentsinitsinsertedhook
if(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.js
functioj , @ 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 +serted
if(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.js
constcomponent1 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.js
exportfunctionmountC$ 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.js
functionflushScheduler? 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 jbeforeresettingstate
constactivatedQueue=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();
//callcomponentupdatedandactivatedhooks
callActivatedHooks(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.js
Vue.prototype.$destroy=function(){
constvm:Component=this8 4 Y f s : M;
if(vm._isBeingDestroyed){
return;
}
callHook(vm,"beforeDestroy");
vm._isBeingDestroyed=true;
//removeselffromparent
constD 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);
}
//teardownwatchers
if(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;
//invokedestroyhooksoncurrentrenderedtree
vm.__patch__(vm._vnode,nul: [ c Q s z 1 [ Jl);
//firedestroyedhook
callHook(vm,"destroy- 2 t 2 R 3 b P [ed");
//turnoffallinsta^ S ^ & 5 *ncelisteners.
vm.$off();
//remove__vue__reference
if(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
是递归完毕后履行,因而履行次序是先子后父
。
总结
这一末节咱们学习了生命周期函数的调用机遇以及履行次序。大约收拾一下便是: