对于 Vue 的生命周期,可以阅读如下流程图(图中不包含activated、deactivated,这两个生命周期与keepAlive相关,会在之后系列文章中介绍)。本篇会从源码的角度分析 Vue 的生命周期函数都在什么时间节点执行以及做了什么。

callHook专门用来执行生命周期函数,只需传入 Vue 实例和钩子函数名称即可。
export function callHook (vm: Component, hook: string) {
const handlers = vm.$options[hook]
const info = `${hook} hook`
if (handlers) {
for (let i = 0, j = handlers.length; i < j; i++) {
invokeWithErrorHandling(handlers[i], vm, null, vm, info)
}
}
if (vm._hasHookEvent) vm.$emit('hook:' + hook)
}
export function invokeWithErrorHandling (
handler: Function,
context: any,
args: null | any[],
vm: any,
info: string
) {
let res = args ? handler.apply(context, args) : handler.call(context)
// ...
return res
}
了解callHook方法之后,我们从 init 开始探讨生命周期。
beforeCreate & created
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// ...
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')
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
在 init 中调用了两个生命周期函数beforeCreate、created。可以看到最初对 events 进行了初始化(还包括 $attrs、$listeners等等),随后调用了beforeCreate,此时定义的数据还未初始化。紧接着对injections、data、props、provide进行了初始化,之后再调用created,此时就可以获取到数据了。接下来是挂载阶段。
beforeMount & mounted
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
// ...
callHook(vm, 'beforeMount')
let updateComponent = () => {
vm._update(vm._render(), hydrating)
}
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
在调用mountComponent最初会执行beforeMount,那mounted在什么时间执行呢?有两处地方:(1)当前实例为根节点时,在mountComponent结束之前执行mounted。(2)在子组件patch结束之前会调用invokeInsertHook,遍历insertedVnodeQueue并执行insert钩子函数,而在insert钩子函数中调用了mounted。
return function patch (oldVnode, vnode, hydrating, removeOnly) {
// ...
createElm(
vnode,
insertedVnodeQueue,
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
)
// ...
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
return vnode.elm
}
function invokeInsertHook (vnode, queue, initial) {
// delay insert hooks for component root nodes, invoke them after the
// element is really inserted
if (isTrue(initial) && isDef(vnode.parent)) {
vnode.parent.data.pendingInsert = queue
} else {
for (let i = 0; i < queue.length; ++i) {
queue[i].data.hook.insert(queue[i])
}
}
}
function insert (vnode: MountedComponentVNode) {
const { context, componentInstance } = vnode
if (!componentInstance._isMounted) {
componentInstance._isMounted = true
callHook(componentInstance, 'mounted')
}
// ...
}
那insertedVnodeQueue又是什么鬼?它实际上是在 patch 过程中不断添加的一个队列,比如在createElm中,如果vnode.data.hook.insert存在,就将此vnode添加到队列中。另外在组件 patch 的createComponent中,会将vnode添加到队列中。因此我们可以知道,子组件的mounted生命周期函数要先于父组件执行。
function createElm (...) {
// ...
if (isDef(vnode.data)) invokeCreateHooks(vnode, insertedVnodeQueue)}
// ...
}
function invokeCreateHooks (vnode, insertedVnodeQueue) {
// ...
i = vnode.data.hook // Reuse variable
if (isDef(i)) {
if (isDef(i.create)) i.create(emptyNode, vnode)
if (isDef(i.insert)) insertedVnodeQueue.push(vnode)
}
}
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue)
// ...
}
}
function initComponent (vnode, insertedVnodeQueue) {
vnode.elm = vnode.componentInstance.$el
if (isPatchable(vnode)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
setScope(vnode)
} else {
registerRef(vnode)
// make sure to invoke the insert hook
insertedVnodeQueue.push(vnode)
}
}
beforeUpdate & updated
接下来探讨beforeUpdate、updated,mountComponent执行时会执行new Watcher(),传入一个参数,定义的 before 函数中调用了beforeUpdate,那么 before 函数是什么时候调用呢?
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
// ...
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
// ...
}
在observer/scheduler.js中在nextTick(之后会提到)调用之前会调用flushSchedulerQueue,在该函数中遍历渲染Watcher,调用 before 函数(相当于调用了beforeUpdate)。之后调用callUpdatedHooks函数(相当于调用了updated)。以上过程均发生在数据更新之后。
const queue: Array<Watcher> = []
function flushSchedulerQueue () {
// ...
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
if (watcher.before) {
watcher.before()
}
}
const updatedQueue = queue.slice()
callUpdatedHooks(updatedQueue)
}
function callUpdatedHooks (queue) {
let i = queue.length
while (i--) {
const watcher = queue[i]
const vm = watcher.vm
// vm._watcher === watcher表示是渲染watcher
if (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'updated')
}
}
}
beforeDestroy & destroyed
最后还有beforeDestroy、destroyed。在lifecycle.js中给 Vue 原型上定义了$destroy方法(该方法在 destroy 钩子函数中执行)
Vue.prototype.$destroy = function () {
const vm: Component = this
if (vm._isBeingDestroyed) {
return
}
callHook(vm, 'beforeDestroy')
vm._isBeingDestroyed = true
// ...
// 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()
// ...
}
在$destroy中首先调用了beforeDestroy,之后又做了解除父子关系、递归销毁子组件等事情,最后调用destroyed,组件销毁过程结束。