从源码深入解析vue生命周期执行机制
前言最近在研究vue2的源码,有点紧张又有点兴奋,希望以后能继续坚持下去。本文就是通过源码分析整个生命周期执行的机制。文中如有错误,敬请指出。非常感谢。如果对你有帮助,请给我一个赞,谢谢。前置知识//子组件varsub={template:'
'}//父组件newVue({components:{sub},template:`
`})子组件实例vm{$vnode指的是父vnode,即父组件中的
例子这个vnode_vnode指的是渲染的vnode,也就是自己渲染的元素,也就是例子中的
}beforeCreate和createdhooks。我们从入口分析开始。newVue时,原型会执行_init的初始化方法。functionVue(options){...this._init(options);}Vue.prototype._init=function(options){varvm=this;初始化生命周期(虚拟机);初始化事件(虚拟机);初始化渲染(虚拟机);callHook(vm,'beforeCreate');初始化注入(vm);初始状态(虚拟机);初始化提供(虚拟机);callHook(vm,'创建');...};我们看一下他的_init方法,这里简化了代码,和生命周期无关,我们看到initLifecycle(vm);initEvents(vm);initRender(vm);然后执行callHook(vm,'beforeCreate')方法,这里触发了对vue实例的beforeCreate钩子的执行,我们看一下callHook的实现。之后,将通过该函数传入不同的生命周期参数,实现所有生命周期的执行。functioncallHook(vm,hook){pushTarget();varhandlers=vm.$options[hook];var信息=挂钩+“挂钩”;if(handlers){for(vari=0,j=handlers.length;i
{})自定义事件调用也会在这里被执行。之后,initInjections(vm);初始状态(虚拟机);初始化提供(虚拟机);这里是在响应式绑定组件上的inject、data、props、watches、computed等属性后,执行创建的生命周期钩子。####父子组件的执行顺序我们知道,在patch过程中,父组件遇到组件vnode时,组件vnode会执行createComponent方法,然后实例化子组件构造器,同时执行一个整套vue初始化流程,因为parent比child先创建,所以执行顺序是parentbeforeCreate>parentcreated>childbeforeCreate>childcreated##beforeMount和mountedhook组件需要执行$mount(mountComponent)cteating之后的方法,然后执行里面的render和patch方法,用于挂载组件。函数mountComponent(vm,el,hydrating){...vm.$el=el;callHook(vm,'beforeMount');变种更新组件;updateComponent=function(){vm._update(vm._render(),保湿);};...if(vm.$vnode==null){vm._isMounted=true;callHook(vm,'挂载');}returnvm}这里会在patch之前执行beforeMounthook,必须执行这个函数mountedhook是在vm.$vnode为null的情况下,我们知道的$vnode是父组件vnode的组件。但是我们知道子组件都有$vnode,那么他会在哪里触发mountedhook呢?实际上,vue的根实例在通过createElm创建真实dom时插入文档时,会传入insertedVnodeQueue在递归处理Component实例中收集子组件,最后将整个真实dom插入文档后,invokeInsertHook为用于遍历执行子组件挂载的钩子。最后root实例的\$vnode为null,所以挂载在最后。functionpatch(oldVnode,vnode,hydrating,removeOnly){letinsertedVnodeQueue=[]letisInitialPatch=falseif(子组件第一次创建时){isInitialPatch=true...}else{createElm(vnode,insertedVnodeQueue,//根实例creation在realdom中会传入insertedVnodeQueue来收集子组件的实例oldElm._leaveCb?null:parentElm,nodeOps.nextSibling(oldElm));}...invokeInsertHook(vnode,insertedVnodeQueue,isInitialPatch);returnvnode.elm}functioninvokeInsertHook(vnode,queue,initial){if(isTrue(initial)&&isDef(vnode.parent)){//因为子组件生成了trueDom之后,就会来到这里。当判断组件是第一次渲染,并且有父vnode时//不会遍历队列,而是将队列保存在data.pendingInsert属性中,供后续父实例获取当前队列//遍历插入钩子只有在使用根实例时才会执行,即触发所有子组件的挂载钩子。vnode.parent.data.pendingInsert=队列;}else{for(vari=0;iinsert:functioninsert(vnode){varcontext=vnode.context;varcomponentInstance=vnode.componentInstance;如果(!componentInstance._isMounted){componentInstance._isMounted=true;callHook(componentInstance,'mounted');}if(vnode.data.keepAlive){if(context._isMounted){queueActivatedComponent(componentInstance);}else{activateChildComponent(componentInstance,true/*直接*/);}}},destroy:functiondestroy(vnode){varcomponentInstance=vnode.componentInstance;如果(!componentInstance._isDestroyed){如果(!vnode.data.keepAlive){componentInstance.$destroy();}else{deactivateChildComponent(componentInstance,true/*direct*/);}}}};这里,当父子组件嵌套时,深度遍历会执行patch函数,子组件真正的Dom会先插入到父元素中,所以子组件实例会先插入到insertedVnodeQueue中父子组件执行顺序因为patch函数先由parent执行,所以beforeMount是parent>child,子组件先插入insertedVnodeQueue队列,最后在遍历过程中,子组件的mmouted会先执行,所以mountedchild>Parent,所以顺序是parentbeforeMount>childbeforeMount>childmounted>parentmounted整体初始渲染顺序是parentbeforeCreate->parentcreated->parentbeforeMount->childbeforeCreate->childcreated->childbeforeMount->childmounted->parentmountedbeforeUpdate和updated钩子都在组件更新时触发。$mount(mountComponent)挂载时,有这么一段代码callHook(vm,'beforeUpdate');}}},true/*isRenderWatcher*/);这里是创建组件的renderingwatcher,并传入before函数,也就是beforeUpdatehook的执行。我们知道,当父子组件更新时,会调用watcher方法update,根据响应式系统将watcher推入队列,在下一个tick会执行函数flushSchedulerQueue遍历队列进行更新,并且将执行before函数以触发beforeCreate挂钩。并通过watcher.vm获取组件实例,并触发updatedhook。Watcher.prototype.update=functionupdate(){...queueWatcher(this);};functionqueueWatcher(watcher){...nextTick(flushSchedulerQueue);}functionflushSchedulerQueue(){...for(index=0;}}indexchildbeforeUpdate,而在调用UpdatedHooks时,在while循环中,callHook(vm,'updated')是通过递减lastwatcher来执行的,所以整体的执行顺序是parentbeforeUpdate>childbeforeUpdate>childupdated>parentupdatedbeforeDestroy和destroyedhooks。两个钩子在组件销毁时执行,组件更新时会执行新旧vnode的diff算法,逻辑在patchVnode在updateChildren函数中,具体逻辑可以去源码中查看,因为在比较的时候,会删除一些无用的节点,会触发removeVnodes函数,调用invokeDestroyHook函数执行hook中的数据组件vnode.hook.destroy(参见上面代码中组件vnode中安装的钩子)functionremoveVnodes(parentElm,vnodes,startIdx,endIdx){for(;startIdx<=endIdx;++startIdx){varch=vnodes[开始Idx];如果(isDef(ch)){如果(isDef(ch.tag)){removeAndInvokeRemoveHook(ch);invokeDestroyHook(ch);}else{//文本节点removeNode(ch.elm);}}}}函数invokeDestroyHook(vnode){vari,j;vardata=vnode.data;if(isDef(data)){if(isDef(i=data.hook)&&isDef(i=i.destroy)){i(vnode);}for(i=0;ichildbeforeDestroy,然后vm.__patch__(vm._vnode,null)会递归找到它的子组件,执行data.hook.destroy,所以会先执行子组件的destroy钩子,父组件会执行parentbeforeDestroy>childbeforeDestroy>childdestroyed>parentdestroyeddeactivated和activated钩子,应用这两个钩子在keep-alive组件包裹的组件下,类似于mounted和destroyed钩子。在代码判断中,通过vnode.data.keepLive来区分常见的非缓存组件,然后执行不同的hooks上一篇写的是对vue的10种生命周期运行机制的总结。如果您有幸阅读,如有不妥之处,欢迎评论或私信讨论。觉得不错就点个赞哈哈