本篇代碼位於vue/src/core/instance/lifecycle.jsvue
初步探索完了核心類的實現以後,接下來就要開始深刻到Vue實現的具體功能部分了。在全部的功能開始運行以前,要來理解一下Vue的生命週期,在初始化函數中全部功能模塊綁定到Vue的核心類上以前,最早開始執行了一個初始化生命週期的函數initLifecycle(vm)
,先來看看這個函數作了些什麼。node
// 導出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
函數很是簡單明瞭,主要是在生命週期開始以前設置一些相關的屬性的初始值。一些屬性將在以後的生命週期運行期間使用到。git
生命週期的開始除了設置了相關屬性的初始值以外,還爲類原型對象掛載了一些方法,包括私有的更新組件的方法和公用的生命週期相關的方法。這些方法都包含在 lifecycleMixin
函數中,還記得這也是在定義核心類以後執行的那些函數之一,也來看看它的內容。github
// 導出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
函數實現了三個原型繼承方法:數組
這個函數用於更新組件,實現數據和元素節點的無刷新更新,涉及到虛擬節點相關的一些內容,具體實現留給將來研究虛擬節點和數據更新時再深刻探索。架構
實現組件強制刷新,這個方法是從實例上設置的watcher對象方法中引用而來,在生命週期初始化的時候爲實例設置了一個私有的_watcher屬性,在觀察者系統的功能模塊中具體實現了這一對象,也放到之後在去深刻了解。這裏只要知道能夠調用這個共有的API實現手動更新組件。ide
實例銷燬方法。在剛開始討論生命週期的開啓時,就瞭解到了這個銷燬Vue實例組件的方法,凡事都善始善終,從這裏能夠明白無誤的認識到,Vue實例是一個生命過程。那麼在Vue的生命過程當中有哪些重要的階段,是接下來要繼續探索的內容。函數
最明白無誤的生命週期過程在官方文檔中有介紹,這裏再貼上這張經典的圖示來作個記念。學習
對照生命週期圖示中呈現的各類鉤子函數,從源碼總結了他們的調用時機,順便又學習一遍鉤子執行的線路:this
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
複製代碼
從 new Vue()
建立實例開始 ,在執行 _init()
方法時開始初始化了生命週期、事件和渲染。緊接着就調用了 beforeCreate
鉤子函數。此時與數據相關的屬性都尚未初始化 ,因此在這個階段想要用獲取到組件的屬性是沒法成功的。
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
複製代碼
在 beforeCreate
調用後,繼續初始化屬性注入、狀態、子組件屬性提供器。而後當即調用 created
鉤子,這個時候數據可訪問了,可是尚未開始渲染頁面,適合一些數據的初始化操做。另外provide和injection主要爲高階插件/組件庫提供用例。並不推薦直接用於應用程序代碼中,因此此刻咱們主要注意的是觀察器的初始化完成。 到這一步以後,就開始進入渲染流程。
渲染的執行流程稍微複雜一些,實例裝載方法 $mount
是根據平臺的不一樣需求而分別定義的,在執行 $mount
方法的時候,開始裝載組件,具體內容在 mountComponent
函數中,在此函數的最開始時渲染虛擬節點以前就調用了 beforeMount
鉤子,而後開始執行 updateComponent
來渲染組件視圖。
緊接着上面視圖的渲染完成,mounted
鉤子被調用。在這個鉤子中還調用了內部的插入鉤子渲染引用的子組件,這以後就開始處於生命週期的正常運轉期。在這個時期內觀察器系統開始監控全部的數據更新,進入數據更新並從新渲染視圖的循環中。
在觀察器的做用下,若是有數據的更新時就會先調用 beforeUpdate
鉤子。
當數據更新而且完成視圖渲染後調用 updated
鉤子。這個鉤子和上面的鉤子會一直在生命週期運轉期裏不斷被觸發。
activated
和 deactivated
這兩個特殊鉤子是在使用 keep-alive
組件的時候纔有效。分別在組件被激活或切換到其餘組件的時候被調用。 使用 keep-alive
模式在切換到不一樣組件視圖的過程當中不會進行從新加載,這就意味着其餘的鉤子函數都不會被調用,若是在離開頁面和進入頁面的時候執行某些操做,這兩個鉤子就很是有用。
beforeDestroy
和 destroyed
鉤子與上面的兩個鉤子相對應,是在普通模式下會有效的鉤子。實例的生命週期的最後階段就是執行銷燬,在銷燬以前調用 beforeDestroy
。而後清除了全部的數據引用、觀察器和事件監聽器。最後調用 destroyed
宣告生命週期的徹底終止。
以前看過不少次Vue的生命週期圖,但在學習源碼以前並無特別深的感觸,如今隨着探索源碼的深刻,終於感受到在慢慢了解這個過程的意義。整個生命週期的構建過程並非最難的實現部分,但它是整個架構的背後支撐力量,有了生命週期的正常運轉,才能一步步地實現接下來要學習的各類功能。