生命周期的是我们在开发中不可回避的话题。了解生命周期也可以让我们知道什么阶段可以做什么,以及更好的解决项目中遇到的问题。
本文包含个人理解内容,希望大家批判性阅读,如有问题欢迎交流~

文章说明
- 每一个 · 后跟的生命周期钩子可以点击进入Vue源码的调用函数或代码行。
- 上述生命周期钩子后的加粗字体是Vue文档对钩子函数的简要解释。
- 文中引入的Vue源码均进行了不同程度的简化,仅供参考,详细代码可以通过第一条说明位置点击查看。
beforeCreate & created

Vue.prototype._init = function (options?: Object) {
// ...
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
// ...
}上面的代码是Vue实例化时调用的方法,从代码中我们可以看到,Vue的实例化阶段执行了 beforeCreate 和 created 两个钩子函数,下面分别来说。
beforeCreate
- beforeCreate:官方的解释是,在实例(Vue)初始化之后,数据观测(data observer)和 event/watcher 事件配置之前被调用
从上面的代码里可以看到,在调用 beforeCreate 之前,调用了三个函数,分别是初始化生命周期、事件和render。需要注意的是,此处的 initEvents(点击查看源码) 初始化的并不是自定义的事件,而是Vue一些原生事件和方法。
所以此时定义在 data 中的属性、methods中的方法等等都还不能访问。
created
- created:官方解释是在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),property 和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el property 目前尚不可用。
在 beforeCreate之后,created之前,执行了 initInjections(vm)、initState(vm)、initProvide(vm) 三个方法
- initInjections(vm):初始化注入信息
- initState(vm):初始化props、methods、data、computed、watch等内容
- initProvide(vm):初始化provide信息
但是此处留一个疑问,我也没有搞明白,为什么是先初始化 inject,后初始化 provide?欢迎大佬们指导(抱拳.jpg)
从上面调用的方法可以看到,在 created 阶段,我们已经可以访问到自定义的一些 数据、属性和方法等内容,但是依然没有DOM。此阶段我们可以获取一些页面初始化时就需要显示的数据,但是不能操作DOM。
beforeMount & mounted

export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
......
callHook(vm, 'beforeMount')
let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
// 需要对组件渲染进行性能追踪时执行逻辑
} else {
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}beforeMount
- beforeMount:在挂载之前被调用:相关的render函数首次被调用(该钩子在服务端渲染期间不被调用)
在 created 之后,beforeMount 之前,会检查 el 属性,el 属性决定了我们最后要把DOM挂载到哪儿,如果不存在 el,则检查是否手动调用了 vm.$mount(el) 。两个条件满足其一,则进行下一步,否则停止执行。下一步会检查 template 属性是否存在,如果不存在则检查外层是否存在满足 el 传入选择器条件的 HTML 元素,两个条件满足其一,则进入 mount 过程,否则报错。
满足以上条件后,调用 beforeMount 钩子
beforeMount 之后,通过 vm._render() 将代码渲染为 VNode,然后通过 vm._update() 将 VNode patch 到真实的 DOM。完成后执行 mounted 钩子。
mounted
- mounted:实例被挂载以后调用,el 被替换为 vm.$el
mounted 不会保证所有的子组件都挂载完成。如果希望等到整个视图都渲染完毕,可以使用 $nextTick
mounted: function () {
this.$nextTick(function () {
// Code that will run only after the
// entire view has been rendered
})
}
这里有一点容易懵逼的是,在Vue文档中写的是在 beforeMount 之后用新创建的 vm.$el 替换 el,但是上面的源码中却看到在 beforeMount 之前执行了 vm.$el = el 。关于这个问题,在测试beforeMount 和 mounted 两个钩子中输出 $el 后,我的个人理解是:
- 在 beforeMount 之前对 $el 的赋值只是把通过 el 选择器拿到的DOM给了 $el,但此时并没有我们写的其他页面内容,所以拿到相当于只是一个空壳。
- 在 beforeMount 之后,将代码渲染为 VNode,并通过 vm._update() patch 到真实DOM,通过代码可以看到,在 vm._update 中是更新过 vm.$el 的,所以此时的 $el 拿到的才是完成的 DOM 结构。因此文档说的是在 beforeMount 之后将 el 替换为新创建的 $el。
所以,这两个生命周期的执行逻辑可以总结为:
- vm.$el = el
- 执行 beforeMount()
- 调用 vm._render() 渲染 VNode
- vm._update() 把 VNode patch 到真实的 DOM,并更新 $el
- 执行 mounted()
beforeUpdate & updated

// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
/**
* Flush both queues and run the watchers.
*/
function flushSchedulerQueue () {
currentFlushTimestamp = getNow()
flushing = true
let watcher, id
// Sort queue before flush.
// This ensures that:
// 1. Components are updated from parent to child. (because parent is always
// created before the child)
// 2. A component's user watchers are run before its render watcher (because
// user watchers are created before the render watcher)
// 3. If a component is destroyed during a parent component's watcher run,
// its watchers can be skipped.
// call component updated and activated hooks
callActivatedHooks(activatedQueue)
callUpdatedHooks(updatedQueue)
}
function callUpdatedHooks (queue) {
let i = queue.length
while (i--) {
const watcher = queue[i]
const vm = watcher.vm
if (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'updated')
}
}
}beforeUpdate
- beforeUpdate:数据更新时调用,发生在虚拟DOM打补丁之前。这里适合在更新之前访问现有的DOM
上面代码对 vue 实例创建了一个监听,并将 updateComponent 作为回调,在实例数据有更新时去更新DOM。
但在此之前,有一个判断条件,也就是 before 参数中的内容,首先判断当前 _isMounted 为 true,也就是保证现在组件已经 mounted,同时 _isDestoryed 为 false,也就是保证现在组件数据不是因为要销毁才发生的改变。满足这两个条件后调用 beforeMount 钩子。
updated
- updated:数据更改导致的虚拟DOM重新渲染和打补丁,之后调用该钩子。此时组件的DOM已经更新,所以可以执行依赖于DOM的操作
updated 调用在 callUpdatedHooks() 方法中,callUpdatedHooks() 在 flushSchedulerQueue() 中被调用。
flushSchedulerQueue() 主要是对要更新的队列进行预处理,从上面代码保留的注释中我们可以看到简要的处理逻辑。
在预处理完成后,将处理过的队列作为参数调用 callUpdatedHooks() ,方法内部对更新队列进行遍历,然后对满足条件的队列调用 updated 钩子。
beforeDestroy & destroy

Vue.prototype.$destroy = function () {
const vm: Component = this
if (vm._isBeingDestroyed) {
return
}
callHook(vm, 'beforeDestroy')
vm._isBeingDestroyed = true
// remove self from parent
const parent = vm.$parent
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
remove(parent.$children, vm)
}
// teardown watchers
if (vm._watcher) {
vm._watcher.teardown()
}
let i = vm._watchers.length
while (i--) {
vm._watchers[i].teardown()
}
// remove reference from data ob
// frozen object may not have observer.
if (vm._data.__ob__) {
vm._data.__ob__.vmCount--
}
// call the last hook...
vm._isDestroyed = true
// invoke destroy hooks on current rendered tree
vm.__patch__(vm._vnode, null)
// fire destroyed hook
callHook(vm, 'destroyed')
// turn off all instance listeners.
vm.$off()
// remove __vue__ reference
if (vm.$el) {
vm.$el.__vue__ = null
}
// release circular reference (#6759)
if (vm.$vnode) {
vm.$vnode.parent = null
}
}
}
beforeDestroy 在 $destroy 方法最开始调用,此时销毁还没有开始,所以当前实例完全可用。
beforeDestroy 之后,将 _isBeingDestroyed 置为true,同时开始执行一系列的销毁过程,主要包括:从当前组件的 $parent 中删除自己、移除watch、调用当前渲染 VNode 的销毁钩子。
上述过程执行完以后,调用 destroyed 钩子。
destoryed
- destroyed:实例销毁后调用。该钩子被调用后,对应的Vue实例所有指令都被解绑,所有的事件监听器被移除,所有的子实例被销毁
至此,生命周期介绍完了,通过了解生命周期,我们可以简单的总结出以下几点:
- 在created中可以访问到自定义的数据、方法、计算属性、监听等内容,但是没有DOM,此时我们可以进行页面渲染所需数据的获取工作。
- 执行mounted时,DOM已经渲染完成,可以进行DOM的更新动作。
- 在destroyed中可以进行DOM的销毁工作。
了解了每个阶段可以做什么,能够很大程度上减少我们写代码中遇到的问题。如发现文章中的问题,欢迎交流、指出~

参考内容:
感谢大佬们文章的帮助~