Vue源碼探究-生命週期

Vue源碼探究-生命週期

本篇代碼位於vue/src/core/instance/lifecycle.js

初步探索完了核心類的實現之後,接下來就要開始深入到Vue實現的具體功能部分了。在所有的功能開始運行之前,要來理解一下Vue的生命週期,在初始化函數中所有功能模塊綁定到Vue的核心類上之前,最先開始執行了一個初始化生命週期的函數initLifecycle(vm),先來看看這個函數做了些什麼。

生命週期初始化屬性

// 導出initLifecycle函數,接受一個Component類型的vm參數
export function initLifecycle (vm: Component) {
  // 獲取實例的$options屬性,賦值爲options變量
  const options = vm.$options

 // 找到最上層非抽象父級 
  // locate first non-abstract parent
  // 首先找到第一個父級
  let parent = options.parent
  // 判斷是否存在且非抽象
  if (parent && !options.abstract) {
    // 遍歷尋找最外層的非抽象父級
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    // 將實例添加到最外層非抽象父級的子組件中
    parent.$children.push(vm)
  }

  // 初始化實例的公共屬性
  // 設置父級屬性,如果之前的代碼未找到父級,則vm.$parent爲undefined
  vm.$parent = parent
  // 設置根屬性,沒有父級則爲實例對象自身
  vm.$root = parent ? parent.$root : vm

  // 初始化$children和$refs屬性
  // vm.$children是子組件的數組集合
  // vm.$refs是指定引用名稱的組件對象集合
  vm.$children = []
  vm.$refs = {}

  // 初始化一些私有屬性
  // 初始化watcher
  vm._watcher = null
  // _inactive和_directInactive是判斷激活狀態的屬性
  vm._inactive = null
  vm._directInactive = false
  // 生命週期相關的私有屬性
  vm._isMounted = false
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}

initLifecycle 函數非常簡單明瞭,主要是在生命週期開始之前設置一些相關的屬性的初始值。一些屬性將在之後的生命週期運行期間使用到。

生命週期初始化方法

生命週期的開始除了設置了相關屬性的初始值之外,還爲類原型對象掛載了一些方法,包括私有的更新組件的方法和公用的生命週期相關的方法。這些方法都包含在 lifecycleMixin 函數中,還記得這也是在定義核心類之後執行的那些函數之一,也來看看它的內容。

// 導出lifecycleMixin函數,接收形參Vue,
// 使用Flow進行靜態類型檢查指定爲Component類
export function lifecycleMixin (Vue: Class<Component>) {
  // 爲Vue原型對象掛載_update私有方法
  // 接收vnode虛擬節點類型參數和一個可選的布爾值hydrating
  Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    // 定義實例變量
    const vm: Component = this
    
    // 下面三條賦值操作主要是爲了存儲屬性
    // 實例的$el屬性賦值給prevEl變量,這是新傳入的實例掛載元素
    const prevEl = vm.$el
    // 實例的_vnode屬性賦值給prevVnode變量,儲存的舊虛擬節點
    const prevVnode = vm._vnode
    // 將activeInstance賦值給prevActiveInstance變量,激活實例
    // activeInstance初始爲null
    const prevActiveInstance = activeInstance

    // 下面是針對新屬性的賦值
    // 將新實例設置爲activeInstance
    activeInstance = vm
    // 將傳入的vnode賦值給實例的_vnode屬性
    // vnode是新生成的虛擬節點數,這裏把它儲存起來覆蓋
    vm._vnode = vnode
    // 下面使用到的Vue.prototype .__ patch__方法是在運行時裏注入的
    // 根據運行平臺的不同定義
    // Vue.prototype.__patch__ is injected in entry points
    // based on the rendering backend used.
    // 如果prevVnode屬性不存在說明是新創建實例
    // 執行實例屬性$el的初始化渲染,否則更新節點
    if (!prevVnode) {
      // 如果舊的虛擬節點不存在則調用patch方法
      // 傳入掛載的真實DOM節點和新生成的虛擬節點
      // initial render
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
      // 否則執行虛擬節點更新操作,傳入的是新舊虛擬節點
      // updates
      vm.$el = vm.__patch__(prevVnode, vnode)
    }

    // 將之前的激活實例又賦值給activeInstance
    activeInstance = prevActiveInstance
    // 更新__vue__屬性的引用
    // update __vue__ reference
    // 如果存在舊元素則設置它的__vue__引用爲null
    if (prevEl) {
      prevEl.__vue__ = null
    }
    // 如果實例的$el屬性存在,設置它的__vue__引用爲該實例
    if (vm.$el) {
      vm.$el.__vue__ = vm
    }
    // 如果父節點是一個高階組件,也更新它的元素節點
    // if parent is an HOC, update its $el as well
    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
      vm.$parent.$el = vm.$el
    }
    // 更新的鉤子由調度器調用,以確保在父更新的鉤子中更新子項。
    // updated hook is called by the scheduler to ensure that children are
    // updated in a parent's updated hook.
  }

  // 爲Vue實例掛載$forceUpdate方法,實現強制更新
  Vue.prototype.$forceUpdate = function () {
    const vm: Component = this
    if (vm._watcher) {
      vm._watcher.update()
    }
  }

  // 爲Vue實例掛載$destroy方法
  Vue.prototype.$destroy = function () {
    // 定義實例變量
    const vm: Component = this
    // 如果實例已經在銷燬中,則返回
    if (vm._isBeingDestroyed) {
      return
    }
    // 調用beforeDestroy鉤子
    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
    }
  }
}

lifecycleMixin 函數實現了三個原型繼承方法:

私有方法 _update

這個函數用於更新組件,實現數據和元素節點的無刷新更新,涉及到虛擬節點相關的一些內容,具體實現留給未來研究虛擬節點和數據更新時再深入探索。

公用方法 $forceUpdate

實現組件強制刷新,這個方法是從實例上設置的watcher對象方法中引用而來,在生命週期初始化的時候爲實例設置了一個私有的_watcher屬性,在觀察者系統的功能模塊中具體實現了這一對象,也放到以後在去深入瞭解。這裏只要知道可以調用這個共有的API實現手動更新組件。

公用方法 $destroy

實例銷燬方法。在剛開始討論生命週期的開啓時,就瞭解到了這個銷燬Vue實例組件的方法,凡事都有始有終,從這裏可以明白無誤的認識到,Vue實例是一個生命過程。那麼在Vue的生命過程中有哪些重要的階段,是接下來要繼續探索的內容。

生命週期過程

最明白無誤的生命週期過程在官方文檔中有介紹,這裏再貼上這張經典的圖示來做個紀念。

生命週期圖示

生命週期鉤子

對照生命週期圖示中呈現的各種鉤子函數,從源碼總結了他們的調用時機,順便又學習一遍鉤子執行的線路:

  • callHook(vm, 'beforeCreate')
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')

new Vue() 創建實例開始 ,在執行 _init() 方法時開始初始化了生命週期、事件和渲染。緊接着就調用了 beforeCreate 鉤子函數。此時與數據相關的屬性都還沒有初始化 ,所以在這個階段想要用獲取到組件的屬性是無法成功的。

  • callHook(vm, 'created')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')

beforeCreate 調用後,繼續初始化屬性注入、狀態、子組件屬性提供器。然後立即調用 created 鉤子,這個時候數據可訪問了,但是還沒有開始渲染頁面,適合一些數據的初始化操作。另外provide和injection主要爲高階插件/組件庫提供用例。並不推薦直接用於應用程序代碼中,所以此刻我們主要注意的是觀察器的初始化完成。
到這一步之後,就開始進入渲染流程。

  • callHook(vm, 'beforeMount')

渲染的執行流程稍微複雜一些,實例裝載方法 $mount 是根據平臺的不同需求而分別定義的,在執行 $mount 方法的時候,開始裝載組件,具體內容在 mountComponent 函數中,在此函數的最開始時渲染虛擬節點之前就調用了 beforeMount 鉤子,然後開始執行 updateComponent 來渲染組件視圖。

  • callHook(vm, 'mounted')

緊接着上面視圖的渲染完成,mounted 鉤子被調用。在這個鉤子中還調用了內部的插入鉤子渲染引用的子組件,這之後就開始處於生命週期的正常運轉期。在這個時期內觀察器系統開始監控所有的數據更新,進入數據更新並重新渲染視圖的循環中。

  • callHook(vm, 'beforeUpdate')

在觀察器的作用下,如果有數據的更新時就會先調用 beforeUpdate 鉤子。

  • callHook(vm, 'updated')

當數據更新並且完成視圖渲染後調用 updated 鉤子。這個鉤子和上面的鉤子會一直在生命週期運轉期裏不斷被觸發。

  • callHook(vm, 'activated') 和 callHook(vm, 'deactivated')

activateddeactivated 這兩個特殊鉤子是在使用 keep-alive 組件的時候纔有效。分別在組件被激活或切換到其他組件的時候被調用。 使用 keep-alive 模式在切換到不同組件視圖的過程中不會進行重新加載,這就意味着其他的鉤子函數都不會被調用,如果在離開頁面和進入頁面的時候執行某些操作,這兩個鉤子就非常有用。

  • callHook(vm, 'beforeDestroy') 和 callHook(vm, 'destroyed')

beforeDestroydestroyed 鉤子與上面的兩個鉤子相對應,是在普通模式下會有效的鉤子。實例的生命週期的最後階段就是執行銷燬,在銷燬之前調用 beforeDestroy。然後清除了所有的數據引用、觀察器和事件監聽器。最後調用 destroyed 宣告生命週期的完全終止。


之前看過很多次Vue的生命週期圖,但在學習源碼之前並沒有特別深的感觸,現在隨着探索源碼的深入,終於感覺到在慢慢了解這個過程的意義。整個生命週期的構建過程並不是最難的實現部分,但它是整個架構的背後支撐力量,有了生命週期的正常運轉,才能一步步地實現接下來要學習的各種功能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章