前言
本篇是从源码逻辑中看Vue.js的生命周期函数,实际上就是Vue.js官网上生命周期图示详细解释。
具体逻辑梳理
首先看看Vue.js官网上给出的生命周期图示,如下图:
通过之前的Vue逻辑的大致梳理,实际上再看上面的生命周期会认识的更加深入。但是本篇文章会对每一个生命周期被调用处理之前的相关的处理做一个较为明确和详细的梳理。
beforeCreated
从上面的生命周期图示中也会知道整个大概流程,由new Vue触发的,详细的步骤如下:
Vue文件初始化过程中,执行initMixin 定义 Vue.prototype._init原型方法
由new Vue触发调用_init方法
Vue源码中生命周期函数都是使用calHook来调用执行的,即callHook(vm, ‘beforeCreated’)
在beforeCreated函数是在_init原型方法中被调用的
在beforeCreated方法执行前,_init的处理逻辑点主要分为5点:
- vm. options选项对象的合并处理
- initProxy():判断环境是否支持Proxy代理,主要是定义_renderProxy属性
- initLifecycle():初始化生命周期相关的属性,主要定义 root
- initEvents():初始化事件相关的,主要定义_events、_hasHookEvent
- initRender():初始化render函数相关,主要定义vm._c方法、vm. attrs、 slots、$scopedSlots
从Vue源码中beforeCreated函数之前前主要逻辑梳理,得出:
beforeCreated生命周期函数调用时,只做了一些初始化的工作,data、methods等都没有调用处理
created
created生命周期函数也是在_init函数中被调用,在created函数调用之前,主要逻辑处理有:
- initInjections():初始化inject注入,inject是Vue.js 2.2.0+之后提供的一种可实现子实例传值的机制
- initState():初始化state,实际上是依次初始化props、methods、data、computed、watch,并定义_watchers属性
- initProvide():初始化Provide,与inject搭配使用
由created函数执行前的逻辑梳理,可知:
create生命周期函数中,可访问组件中JS部分中props、data、methods、computed
beforeMounted
在最开始的生命周期图示中,可看到beforeMounted之前的处理逻辑相对比较多的,这部分详情之前Vue Render文章中也具体分析过,这里也就不展开了,只说其主要的处理逻辑。
实际上主要是两个方法的调用:
- _init方法最后的处理,vm.$mount
- $mount内部主要的处理是mountComponent函数
而beforeMounted函数就是在mountComponent函数中被调用的,在beforeMounted调用之前,主要逻辑处理有:
- vm.$el:挂载点DOM对象的获取
- 构建$options中的render函数
由beforeMounted函数执行前的逻辑梳理,可知:
create生命周期函数中,DOM相关暂没法处理
mounted
该生命周期函数也是在mountComponent函数中执行的,其调用之前的主要逻辑处理如下:
- 定义updateComponent函数:该函数执行两个原型方法_render和_update
- 调用Watcher构造函数:创建Watcher对象,new Watcher(vm, updateComponent, noop, { before: function() { // codes} })
实际上这边的主要处理,就是Vue的核心之一:通过 vnode并创建render函数。
之后就是判断$vnode是否创建了并调用mounted,从这里的处理逻辑可知:
mounted被调用时,虚拟dom已经创建并替换了原生DOM,你可以处理DOM相关的逻辑了
从源码中来看,主要$vnode不为null就会执行mounted函数
if (vm.$vnode == null) { vm._isMounted = true; callHook(vm, 'mounted'); }
请注意:Vue.js的官网中对与mounted,不会承诺所有的子组件也都一起被挂载(从上面的逻辑中也可略知一二原因),如果你希望等到整个视图都渲染完毕,请使用$nextTick
mounted: function () { this.$nextTick(function () { // Code that will run only after the // entire view has been rendered }) }
beforeUpdate
数据更新时调用,实际上该函数是mountComponent函数调用中定义的,具体如下:
new Watcher(vm, updateComponent, noop, {
before: function before () {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate');
}
}
}, true);
这里简单介绍了Watcher构造函数参数的意义:
Watcher(vm, expOfFn, cb, options, isRenderWatcher)
vm:Vue实例
expOfFn:用于获取vlaue值得函数
cb:回调函数
options:选项,实际上目前只处理了before函数
isRenderWatcher:是否是组件更新相关的Watcher
通过源码逻辑梳理,before只会在Watcher.prototype.update方法中调用,实际逻辑顺序如下:
Watcher.prototype.update -> queueWatcher -> flushSchedulerQueue -> watcher.before
在Watcher.prototype.update方法中,主要是逻辑判断,代码量少这里就直接贴出来了:
Watcher.prototype.update = function update () {
var this$1 = this;
// 判断当前Watcher对象是否是用于处理计算属性的
if (this.computed) {
// codes
} else if (this.sync) {
// 是否是同步
this.run();
} else {
// 其他情况的处理
queueWatcher(this);
}
};
这里实际上有一个需要注意的知识点,就是Watcher对象在哪些情况下会被创建,实际上可大致分为3种:
- 处理计算属性响应式创建的Watcher对象
- $watch API提供的形式创建Watcher对象
- mountComponent罗中对应的创建Watcher对象,用于响应式属性更改后触发视图渲染
这里就before函数调用逻辑实际上queueWatcher函数:
该函数将watcher对象放入queue队列中,并触发flushSchedulerQueue函数。
flushSchedulerQueue函数:
遍历执行queue队列中的watcher对象的before函数以及run实例方法
run实例方法最主要的逻辑就是执行watcher对象的get方法获取value
Watcher get方法的调用会触发_render和_update方法,会生成vNode和render函数
疑问1:Watcher的update方法是在哪里调用的?
实际上是Dep对象的notify方法中触发的,而Dep的notify方法的触发源就是defineProperty的set方法中,即重新对变量进行赋值时。
从这里可以得出:
任何响应式的变量被修改都会触发Watcher的update方法从而触发before方法的执行,进而导致beforeUpdate生命周期函数的执行
beforeUpdate函数中还可以访问数据更新前的DOM
updated
顾名思义,是在数据更新后调用的,同beforeUpdate函数一样,是在Watcher.prototype.update中调用的。
beforeDestroy + destroyed
通过Vue.js源码逻辑的梳理,beforeDestroy生命周期函数是在Vue.prototype.$destroy原型方法中调用的,随之而来的疑问:
什么时候会调用$destroy实例方法呢?
实际上Vue.js源码直接调用$destroy实例方法仅有两处位置:
- componentVNodeHooks对象中定义的关于vnode的生命周期函数destroy中
- pruneCacheEntry函数中,主要是针对keep-alive的(这里暂不去了解)
vnode存在自己的生命周期函数,在destroy中会调用Vue实例方法$destroy,追本溯源,就是vnode的销毁时候会调用。
而涉及到vnode destroy调用主要就是在patch方法、invokeDestroyHook、removeVnodes触发的,这里也就会具体展开,之后会专门看下Vue.js patch(比较复杂的)。
回到$destroy实例方法中,这里具体看下处理逻辑:
Vue.prototype.$destroy = function () {
var vm = this;
if (vm._isBeingDestroyed) {
return
}
callHook(vm, 'beforeDestroy');
vm._isBeingDestroyed = true;
// 从父组件中移除当前实例
var parent = vm.$parent;
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
remove(parent.$children, vm);
}
// 调用Watcher对象的teardown方法,移除与其相关的所有依赖
if (vm._watcher) {
vm._watcher.teardown();
}
var i = vm._watchers.length;
while (i--) {
vm._watchers[i].teardown();
}
// Observer相关
if (vm._data.__ob__) {
vm._data.__ob__.vmCount--;
}
vm._isDestroyed = true;
// 调用当前_vnode虚拟节点的生命周期函数destroy方法
vm.__patch__(vm._vnode, null);
callHook(vm, 'destroyed');
// 当前实例所有事件
vm.$off();
// __vue__值就是当前Vue实例
if (vm.$el) {
vm.$el.__vue__ = null;
}
if (vm.$vnode) {
vm.$vnode.parent = null;
}
};
总结
本篇文章针对Vue.js官网的生命周期图示中涉及到的生命周期有了大概的梳理,可结合Vue.js官网描述可理解的更加深入。