vue的生命周期包含从创建-->挂载渲染-->更新渲染-->销毁的过程,生命周期钩子可以让我们更好的控制vue实例更新渲染的过程。
生命周期
创建beforeCreate|created
- beforeCreate:数据等初始化之前被调用
- created:数据等初始化后被调用。此时,已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。
挂载beforeMount|mounted
- beforeMount:此时,传递的el|template已经被编译为render函数,render函数的优先级最高
- mounted:render触发,新创建的$el替换原来的el
更新beforeUpdate|updated
- beforeUpdate:数据变化后,虚拟dom更新前调用,可以用于获取数据更新前的dom
- updated:re-render,可在此执行依赖于dom的操作;但是此时,并非所有dom都会重绘,可以使用$nextTick
销毁beforeDestroy|destroyed
- beforeDestroy: 实例销毁前调用,可以移除事件监听器、定时器等,避免内存泄漏
- destroyed:实例销毁后调用,所有的子实例都销毁,所有事件被移除
钩子实现
它的实现主要有三个问题:
1. 同类钩子如何合并?
2. 钩子怎么触发?
3. 钩子什么时候触发?
数据合并
对于普通数据parent和child,分别遍历parent和child,执行属性合并
普通合并策略
如果是对象,可以直接合并;如果child对应的属性不存在,就是用parent的属性值;否则,全部是用child的属性值
function mergeOptions(parent, child) {
const options = {};
for(let key in parent) { mergeField(key); }
for(let key in child) {
if (!parent.hasOwnProperty(key)){ mergeField(key); }
}
function mergeField(key) {
if (strats[key]) { // 如生命周期的合并
return options[key] = strats[key](parent[key], child[key])
}
if(typeof parent[key] === 'object' && typeof child[key] === 'object') {
options[key] = {
...parent[key],
...child[key]
}
} else if(child[key] == null){
options[key] = parent[key]
} else {
options[key] = child[key]
}
}
return options
}
特定合并策略
特定合并策略,比如针对生命周期的合并,定义单独的合并策略
const LISECYCLE_HOOKS=['beforeCreate', 'created', 'beforeMount', 'mounted', 'beforeUpdate', 'updated', 'beforeDestroy', 'destroyed']
function mergeHook(parentVal, childVal) {
if (childVal) {
if (parentVal) {
return parentVal.concat(childVal)
} else {
return [childVal]
}
} else {
return parentVal
}
}
let strats = {};
LISECYCLE_HOOKS.forEach(hook => {
strats[hook] = mergeHook;
})
同类钩子合并
所有同类钩子是存放在数组中的,当需要触发时,会遍历执行;它的合并主要涉及全局设置钩子和组件自定义钩子合并
全局设置钩子合并
在初始化全局api的时候,在Vue上定义options用于保存全局相关内容;另外,会定义Vue.mixin方法,用于合并directives|filters|components等
function initGlobalAPI(Vue){
Vue.options = {};
Vue.mixin = function (mixin) {
this.options = mergeOptions(this.options, mixin)
}
}
组件自定义钩子合并
自定义钩子合并,在实例初始化时,将用户传递的options和Vue.options合并绑定在vm.$options上时,钩子也默认被合并
Vue.prototype._init = function(opts) {
const vm = this;
vm.$options = mergeOptions(vm.constructor.options, opts);
...
}
钩子触发
钩子触发,从vm.$options上找到指定的钩子数组,遍历执行
function callHook(vm, hook) {
const handlers = vm.$options[hook];
if (handlers) {
for (let i = 0; i < handlers.length; i++) {
const curHook = handlers[i];
curHook.call(vm)
}
}
}
钩子触发时机
从上述生命周期中可以看到,在数据初始化前后会触发beforeCreated和creatd,而在组件加载前后触发beforeMount和mounted
Vue.prototype._init = function(opts) {
....
callHook(vm, 'beforeCreate');
initState(vm);
callHook(vm, 'created');
...
}
function mountComponent(vm, el){
...
callHook(vm, 'beforeMount');
let updateComponent= () => {
vm._update(vm._render());
}
new Watcher(vm, updateComponent, () => {}, true);
callHook(vm, 'mounted');
}