Vue生命週期總結

這裏主要記錄在平常中對知識的學習,經過結合筆記與自身理解的方式嘗試寫下總結
文章對細節可能不會一一介紹解釋,內容僅做參考
複製代碼

這些天在嘗試開始對Vue源碼的解讀,一點一點去了解框架的設計以及實現思路。今天在編碼時候想了有關生命週期的問題,恰好晚上就看到了相關知識。做爲其中一小步記錄一下javascript

1、生命週期

每一個Vue實例在被建立以前都要通過一系列的初始化過程。例如設置數據監聽、編譯模板、掛載實例到 DOM、在數據變化時更新 DOM 等
在這個過程當中會執行相應的生命週期鉤子函數,給予用戶機會在一些特定的場景下添加本身的邏輯代碼vue

直接貼上官方生命週期圖:java

能夠看出生命週期是描述了一個Vue實例在建立、掛載、註銷、更新的一個流程

2、生命週期鉤子的調用

在源碼中最終執行生命週期的函數是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

  • 根據hook拿到對應的回調函數數組(vue實例在初始化時候,其中有個過程是合併options,在該操做時會收集各個階段的生命週期鉤子函數構成對應的數組,而後將他們都掛載到實例的options中(即vm.$options)。因此在這裏拿到的是一個數組。具體的能夠去看一下代碼)
  • 若是數組有值,遍歷代入invokeWithErrorHandling方法中執行(在方法中咱們能夠看到使用了apply/call將實例vm做爲函數執行上下文傳入,這也是咱們在編寫生命週期回調的時候,不能使用箭頭函數的緣由:箭頭函數的執行上下文指向定義該函數時的上下文,且沒法改變,從而獲取不到實例對象指向)

3、鉤子羅列

查看Vue官網,很容易能夠獲得有以下鉤子:數組

  1. beforeCreate
  2. created
  3. beforeMount
  4. mounted
  5. beforeUpdate
  6. updated
  7. activated
  8. deactivated
  9. beforeDestroy
  10. destroyed
  11. errorCaptured

至於他們都有什麼做用,官網已經寫得很詳細,建議看一下:cn.vuejs.org/v2/api/app

除七、八、11外,其餘的在平時開發中較經常使用到。它們的執行順序跟排列順序同樣框架

4、beforeCreate和created

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鉤子函數

5、beforeMount和mounted

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鉤子函數的執行順序是先子後父

6、beforeUpdate和updated

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')
    }
  }
}
複製代碼

至於深刻,這裏就不解釋啦~ (還不會

7、beforeDestroy和destroyed

beforeDestroy和destroyed鉤子函數的執行時機在組件銷燬的階段
beforeDestroy鉤子函數的執行時機是在destroy函數執行最開始的地方,接着執行了一系列的銷燬動做,包括從parent的children中刪掉自身,刪除watcher,當前渲染的VNode執行銷燬鉤子函數等,執行完畢後再調用destroy鉤子函數 在$destroy的執行過程當中,它又會執行vm.patch(vm._vnode, null)觸發它子組件的銷燬鉤子函數,這樣一層層的遞歸調用

因此destroy鉤子函數執行順序是先子後父,和mounted過程同樣

8、activated和deactivated

activated是keep-alive組件激活時調用

deactivated是keep-alive組件停用時調用

9、總結

經過對整個生命週期的瞭解,就能夠很清晰地知道能夠在什麼階段作什麼事,或者某一操做應該在什麼階段執行

例如在create中進行數據操做,在mounted中進行DOM完成後的操做,在destroyed進行事件解綁和功能註銷

文章篇幅有點多,不少都是一些相關的衍生,只有在進行源碼解讀的時候才比較容易理解。在這裏最重要的是知道每一個生命週期鉤子的時機和做用,其餘都是浮雲~

相關文章
相關標籤/搜索