每一個 Vue 實例在被建立以前都要通過一系列的初始化過程。例如須要設置數據監聽、編譯模板、掛載 DOM 實例、在數據變化時更新 DOM 等。同時在這個過程當中也會運行一些叫作生命週期鉤子的函數,給予用戶機會在一些特定的場景下添加他們本身的代碼。html
源碼中最終執行生命週期的函數都是調用 callHook 方法,它的定義在 src/core/instance/lifecycle.js 中:vue
export function callHook (vm: Component, hook: string) {
pushTarget()
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)
}
popTarget()
}
複製代碼
beforeCreate 和 created 函數都是在實例化階段觸發,在 _init
方法中執行的,它的定義在 src/core/instance/init.js 中:node
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')
// ...
}
複製代碼
能夠看到 beforeCreate 和 created 的鉤子調用是在 initState 的先後,initState 的做用是初始化 props, data, methods, watch, computed 等屬性。顯然 beforeCreate 的鉤子中不能獲取到 props, data 中定義的值,也不能調用 methods 中定義的函數。git
在執行兩個鉤子函數的時候,並無渲染 DOM,因此咱們也不能訪問 DOM,通常來講,若是組件在加載的時候須要和後端有交互,放在這倆個鉤子函數執行均可以,若是是須要訪問 props、data 等數據的話,就須要使用 created 鉤子函數。github
beforeMount 鉤子函數在 DOM 掛載前觸發,在 mountComponent 函數中執行,定義在 src/core/instance/lifecycle.js 中:web
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) {
updateComponent = () => {
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag)
const vnode = vm._render()
mark(endTag)
measure(`vue ${name} render`, startTag, endTag)
mark(startTag)
vm._update(vnode, hydrating)
mark(endTag)
measure(`vue ${name} patch`, startTag, endTag)
}
} 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
}
複製代碼
在執行 vm._render()
函數渲染 VNode 以前,執行了 beforeMount 鉤子函數,執行完後把 VNode push 到真實 DOM 後,執行了 mounted 鉤子。注意代碼註釋中提到,這是手動掛載實例的執行邏輯,真正的組件初始化過程,是執行 invokeInsertHook 函數,定義在 src/core/vdom/patch.js 中:後端
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])
}
}
}
複製代碼
insert 函數定義在 src/core/vdom/create-component.js 中的 componentVNodeHooks 中:api
const componentVNodeHooks = {
// ...
insert (vnode: MountedComponentVNode) {
const { context, componentInstance } = vnode
if (!componentInstance._isMounted) {
componentInstance._isMounted = true
callHook(componentInstance, 'mounted')
}
// ...
},
}
複製代碼
每一個子組件都是在這個鉤子函數中執行 mounted 鉤子函數。而且以前咱們分析過,insertedVNodeQueue 的添加順序是先父後子,因此對於同步渲染的子組件而言,mounted 鉤子的執行順序也是先父後子。數組
beforeUpdate 鉤子函數是在實例數據放生變化 更新 DOM 前觸發,定義在監聽器的 before 函數中:dom
export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component {
// ...
// 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) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
// ...
}
複製代碼
這個鉤子函數要在實例掛載完成後纔會觸發。
updated 鉤子函數的執行時機在 flushSchedulerQueue 函數調用時,它定義在 src/core/observer/scheduler.js 中:
function flushSchedulerQueue () {
// ...
// 獲取到 updatedQueue
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) {
callHook(vm, 'updated')
}
}
}
複製代碼
這裏 updatedQueue 是已更新的數組, callUpdatedHooks 函數會遍歷這個數組,知足條件的會觸發 updated 鉤子函數。
以前提到過,在組件實例化過程當中,會實例化一個渲染的 Watcher 去監聽 vm 上的數據變化從新渲染,在實例化 Watcher 過程當中會判斷 isRenderWatcher,如果則會把當前 watcher 實例賦值給 vm._watcher
,Watcher 類定義在 src/core/observer/watcher.js 中:
export default class Watcher {
// ...
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
// ...
}
}
複製代碼
因爲是渲染相關的 watcher 實例才賦值給 vm._watcher
屬性,所以在 callUpdatedHook 函數中,觸發 updated 鉤子函數前又做了一層判斷,只有 vm._watcher
等於當前 watcher 實例,即當前是渲染相關 watcher 實例,纔會執行 updated 鉤子函數。
beforeDestroy 和 destroyed 鉤子函數都在是組件銷燬階段觸發,經過調用 $destroy
方法執行。方法在組件實例化過程當中,執行 lifecycleMixin 函數時定義,它的定義在 src/core/instance/lifecycle.js 中:
export function lifecycleMixin (Vue: Class<Component>) {
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {}
Vue.prototype.$forceUpdate = function () {}
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 鉤子函數的執行時機是在實例銷燬前,接着執行了一些銷燬動做,包括從父實例移除自身,刪除 watcher,當前渲染的 VNode 執行銷燬鉤子函數等,待執行完畢觸發 destroyed 鉤子函數。
注意這裏在執行完 vm._patch(vm_vnode, null)
後,先觸發了 destroyed 鉤子函數,而後才執行 vm.$off()
移除全部監聽器,所以在 destroyed 階段仍可訪問組件實例。
這兩個生命週期鉤子是 keep-alive 內置組件專門定製的。
加載渲染過程:
更新過程:
銷燬過程:
組件的生命週期大體分爲4個階段,分別是:create、mount、update、destroy,每一個階段的始末又分爲2個鉤子,主要的生命週期鉤子就是由這8個組成。
在組件實例化的過程當中,會執行一系列的初始化方法,這其中有 intMixin, StateMixin, eventsMixin, lifecycleMixin, renderMixin 等。
在 initMixin 方法中會注入 Vue.prototype._init
方法,而在初始化階段會被直接調用,這個函數中主要邏輯除了執行配置合併等操做,在 beforeCreate 鉤子觸發前會執行 initLifecycle, initEvents, initRender 步驟,在 created 鉤子觸發前會執行 initInjections, initState, initProvide 步驟。
在 lifecycleMixin 方法中會注入 Vue.prototype._update
, Vue.prototype.$forceUpdate
, Vue.prototype.$destroy
方法,其中在組件銷燬階段會直接調用 $destroy 函數。
在 web/runtime 相關代碼中,又給 Vue 增長了 $mount 公共函數,該函數返回 mountComponent 函數的執行結果。在 mountComponent 函數中觸發了 beforeMount 鉤子函數,而且實例化 Watcher 監聽器,該監聽器配置了數據發生改變後 before 鉤子,用來觸發 beforeUpdate 鉤子函數。等元素插入 DOM 結構中(vdom/patch.js),調用 insert 函數觸發 mounted 鉤子函數。注意這裏的渲染順序仍然是先子後父。
當監聽到實例的數據發生變化後,會觸發監聽器 watcher 的 before 鉤子觸發 beforeUpdate 鉤子函數。在 flushSchedulerQueue 函數中,更新隊列執行完成後,會觸發 updated 鉤子函數。
在組件預先定義的生命週期中,執行 $destroy 函數先觸發 beforeDestroy 鉤子函數,等組件實例銷燬、刪除數據監聽、並從 DOM 結構中移除,觸發 destroy 鉤子函數。
參考資料: