vue源码分析【5】-vue 生命周期
以下代码和分析过程需要结合vue.js源码查看,通过打断点逐一比对。
1.beforeCreate
先梳理下执行流程
- new Vue前,首先会执行
initMixin(Vue);,但是此时只是挂载一个_init到Vue上,并没有执行_init。 - 接着,执行
new Vue(引入Vue文件后,html或者main.js最终要有这个入口)进入function Vue(options) - 再执行
this._init(options);,进入initMixin中的Vue.prototype._init = function (options)。 - 执行
callHook(vm, 'beforeCreate');,触发beforeCreate钩子函数
callHook:
//触发生命周期钩子函数
function callHook(
vm, //Vue 实例
hook //钩子函数的key
) {
// #7573 disable dep collection when invoking lifecycle hooks
//调用生命周期钩子时禁用dep集合
//Dep.target = _target; //存储
pushTarget();
//获取在vm 中添加的声明周期函数
// 【说明2 vm.$option】
var handlers = vm.$options[hook];
if (handlers) { //数组
for (var i = 0, j = handlers.length; i < j; i++) {
try {
// 执行生命周期函数
/**
* 比如我们在组件的beforeCreate钩子中写了如下代码:
beforeCreate() {
console.log(this,'====beforeCreate钩子函数')
}
handlers[i].call(vm)就会执行beforeCreate钩子函数里面的代码,
并且这里面的this指向传如vm,即Vue
【图1 执行beforeCreate打印this】
*/
debugger
handlers[i].call(vm);
} catch (e) {
handleError(e, vm, (hook + " hook"));
}
}
}
/**
* 判断有没有这种hook方式来调用生命周期钩子:
* <el-select ref="select" @hook:mounted="callback"></el-select>
*/
if (vm._hasHookEvent) {
vm.$emit('hook:' + hook);
}
// 出栈一个pushTarget
popTarget();
}
【说明1 pushTarget】,由于前面没有传参,所以这里_target为空。将Dep的target添加到targetStack,同时Dep的target更新为当前watcher对象
// Dep是订阅者Watcher对应的数据依赖
var Dep = function Dep() {
//uid 初始化为0,每个Dep都有唯一的ID
this.id = uid++;
/* subs用于存放Watcher的依赖 */
this.subs = [];
};
Dep.target = null; //初始化为null,最终通过pushTarget更新
// 目标堆栈
var targetStack = [];
function pushTarget(_target) {
if (Dep.target) { //target 是Watcher; 静态标志
targetStack.push(Dep.target);// 集中管理Dep.target
}
Dep.target = _target;// 更新为当前传入的target
}
popTarget
function popTarget() {
// 出栈一个pushTarget
// pop()删除并返回数组的最后一个元素。
Dep.target = targetStack.pop();
}
【说明2 vm.$option】,可以看到这上面已经有了生命周期的相关函数了,那么,他是在哪里定义的呢?我们往前推,可以看到initMixin(Vue)有这样一段代码:
vm.$options = mergeOptions(
// 解析constructor上的options属性的
resolveConstructorOptions(vm.constructor),
// 这个options是function Vue(options)传进来的,即new Vue()传进来的,在那里,
// 我们会自己在组件上定义相关的生命周期和相关的方法,属性
options || {},
vm
);
【图1 执行beforeCreate打印this】
2. created
执行完beforeCreate接着就会执行created。
在initMixin执行:
callHook(vm, 'created'); //触发created钩子函数
3. beforeMount
在mountComponent(){}执行:
callHook(vm, 'beforeMount');
它的执行顺序是这样的:
- new Vue前,首先会执行
initMixin(Vue);,但是此时只是挂载一个_init到Vue上,并没有执行_init。 - 接着,执行
new Vue(引入Vue文件后,html或者main.js最终要有这个入口)进入function Vue(options) - 再执行
this._init(options);,进入initMixin中的Vue.prototype._init = function (options)。 - 执行
callHook(vm, 'beforeCreate');,触发beforeCreate钩子函数 - 执行
callHook(vm, 'created');,触发created钩子函数 - 执行最后一步的
vm.$mount(vm.$options.el);,执行末尾的mount,Vue.prototype.$mount = function(),生成render渲染函数和真实的dom - 函数的最后,返回
mount.call( this, el, hydrating),这时会进入文件中间的mount函数,Vue.prototype.$mount = function() - 然后执行
mountComponent( this, el, hydrating),挂载组件,渲染页面。 - 此时,我们就可以看到真实的dom了。
4. mounted
在mountComponent(){}执行:
if (vm.$vnode == null) {
vm._isMounted = true;
//执行生命周期函数mounted
// 渲染data
callHook(vm, 'mounted');
}
源码提问
1. ajax请求放在哪个生命周期中?
在created的时候,视图中的DOM并没有渲染出来,此时直接去操作DOM节点,无法找到相关元素。 在mounted中,此时DOM已经渲染出来,可以直接操作DOM节点。 一般情况下都放到mounted中,保证逻辑的统一性。 服务器端渲染因为没有DOM,不支持mounted方法,所以在服务器端渲染的情况下统一放到created中。
2. 何时需要使用beforeDestroy?
- 可能在当前组件使用了$on方法,需要在组件销毁前解绑
- 清除自己定义的定时器
- 解除事件的原生绑定scroll、mousemove…