這裏主要記錄在平常中對知識的學習,經過結合筆記與自身理解的方式嘗試寫下總結
文章對細節可能不會一一介紹解釋,內容僅做參考
複製代碼
這些天在嘗試開始對Vue源碼的解讀,一點一點去了解框架的設計以及實現思路。今天在編碼時候想了有關生命週期的問題,恰好晚上就看到了相關知識。做爲其中一小步記錄一下javascript
每一個Vue實例在被建立以前都要通過一系列的初始化過程。例如設置數據監聽、編譯模板、掛載實例到 DOM、在數據變化時更新 DOM 等
在這個過程當中會執行相應的生命週期鉤子函數,給予用戶機會在一些特定的場景下添加本身的邏輯代碼vue
直接貼上官方生命週期圖:java
能夠看出生命週期是描述了一個Vue實例在建立、掛載、註銷、更新的一個流程在源碼中最終執行生命週期的函數是callHook方法和invokeWithErrorHandling方法,它的定義在src/core/instance/lifecycle和src/core/util/error中能夠看到:node
// src/core/instance/lifecycle
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()
}
// src/core/util/error
export function invokeWithErrorHandling ( handler: Function, context: any, args: null | any[], vm: any, info: string ) {
let res
try {
res = args ? handler.apply(context, args) : handler.call(context)
if (res && !res._isVue && isPromise(res) && !res._handled) {
res.catch(e => handleError(e, vm, info + ` (Promise/async)`))
res._handled = true
}
} catch (e) {
handleError(e, vm, info)
}
return res
}
複製代碼
callHook接收的兩個參數分別爲Vue實例和要觸發的生命週期鉤子名後端
在觸發時api
查看Vue官網,很容易能夠獲得有以下鉤子:數組
至於他們都有什麼做用,官網已經寫得很詳細,建議看一下:cn.vuejs.org/v2/api/app
除七、八、11外,其餘的在平時開發中較經常使用到。它們的執行順序跟排列順序同樣框架
beforeCreate和created函數都是在實例化Vue的階段,在_init方法中執行的,它的定義在src/core/instance/init中:dom
Vue.prototype._init = function (options?: Object) {
...
vm._self = vm
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中定義的函數,而created能夠
在這倆個鉤子函數執行的時候,尚未渲染 DOM,所均訪問不到DOM
通常來講,若是組件在加載的時候須要和後端有交互,放在這倆個鉤子函數執行均可以,若是是須要訪問props、data等數據的話,就須要使用created鉤子函數
beforeMount和mounted函數執行在Vue實例掛載階段,它們的調用時機是在mountComponent函數中,定義在src/core/instance/lifecycle:
export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component {
vm.$el = el
...
callHook(vm, 'beforeMount')
...
// 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鉤子函數,在執行完vm._update()把 VNode patch 到真實 DOM 後,執行mounted鉤子
注意,這裏對mounted鉤子函數執行有一個判斷邏輯,vm.$vnode若是爲null,則代表這不是一次組件的初始化過程,而是咱們經過外部new Vue初始化過程
而那麼對於組件,它的mounted時機在哪兒呢
經過閱讀源碼咱們能夠發現(僞裝你們都閱讀源碼,由於上下文只對生命週期進行總結,因此深刻的就不說啦~),在組件VNode patch到DOM後,會執行invokeInsertHook函數(定義在src/core/vdom/patch.js),會把insertedVnodeQueue裏面保存的全部mounted鉤子函數依次執行一遍
這一些都是題外話了,先記住Vue組件在實例化的時候會先等待子組件的實例化完成,因此insertedVnodeQueue(保存組件的mounted鉤子函數的數組)的添加順序是先子後父
因此對於同步渲染的組件而言,mounted鉤子函數的執行順序是先子後父
beforeUpdate和updated的鉤子函數執行時機都是在數據更新的時候
beforeUpdate的執行時機是在 渲染Watcher 的before函數中,在mountComponent函數中能夠看到(src/core/instance/lifecycle):
export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component {
vm.$el = el
...
callHook(vm, 'beforeMount')
let updateComponent
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
...
return vm
}
複製代碼
這裏有個判斷,也就是在組件已經mounted以後纔會去調用這個鉤子函數
update的執行時機是在flushSchedulerQueue函數調用的時候, 它的定義在src/core/observer/scheduler中:
function flushSchedulerQueue () {
currentFlushTimestamp = getNow()
...
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')
}
}
}
複製代碼
至於深刻,這裏就不解釋啦~ (還不會
beforeDestroy和destroyed鉤子函數的執行時機在組件銷燬的階段
beforeDestroy鉤子函數的執行時機是在destroy函數執行最開始的地方,接着執行了一系列的銷燬動做,包括從parent的children中刪掉自身,刪除watcher,當前渲染的VNode執行銷燬鉤子函數等,執行完畢後再調用destroy鉤子函數 在$destroy的執行過程當中,它又會執行vm.patch(vm._vnode, null)觸發它子組件的銷燬鉤子函數,這樣一層層的遞歸調用
因此destroy鉤子函數執行順序是先子後父,和mounted過程同樣
activated是keep-alive組件激活時調用
deactivated是keep-alive組件停用時調用
經過對整個生命週期的瞭解,就能夠很清晰地知道能夠在什麼階段作什麼事,或者某一操做應該在什麼階段執行
例如在create中進行數據操做,在mounted中進行DOM完成後的操做,在destroyed進行事件解綁和功能註銷
文章篇幅有點多,不少都是一些相關的衍生,只有在進行源碼解讀的時候才比較容易理解。在這裏最重要的是知道每一個生命週期鉤子的時機和做用,其餘都是浮雲~