關於Vue的生命週期函數,目前網上有許多介紹文章,但也都只是分析了表象。這篇文檔,將結合Vue源碼分析,爲何會有這樣的表象。javascript
Vue中的生命週期函數也能夠稱之爲生命週期鉤子(hook)函數,在特定的時期,調用特定的函數。html
隨着項目需求的不斷擴大,生命週期函數被普遍使用在數據初始化、回收、改變Loading狀態、發起異步請求等各個方面。vue
而Vue實例的生命週期函數有beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestpry、destroyed,8個。java
本文假設讀者使用過Vue.js,但對相應的開發經驗不作要求。若是你對Vue很感興趣,殊不知如何下手,建議你先閱讀官方文本node
如下是這篇文章所需的資源,歡迎下載。git
咱們在該頁面來研究,Vue的生命週期函數究竟會在什麼時候調用,又會有什麼不一樣的特性。強烈建議你直接將項目倉庫克隆至本地,並在真機環境中,跑一跑。Vue.js已經添加在版本庫中,所以你不須要添加任何依賴,直接在瀏覽器中打開lifeCycle.html
便可。github
$ git clone https://github.com/AmberAAA/vue-guide
定義template
與data
,使其在能夠在屏幕實時渲染出來。數組
{ //... template: ` <div> <p v-html="info"></p> </div> `, data () { return { info: '' } } //... }
以beforeCreate
爲例,定義所有的證實周期函數。瀏覽器
beforeCreate() { console.group('------beforeCreate------'); console.log('beforeCreate called') console.log(this) console.log(this.$data) console.log(this.$el) this.info += 'beforeCreate called <br>' console.groupEnd(); }
在瀏覽器中打開lifeCycle.html
,點擊掛載組件後,屏幕依次輸出created called
、beforeMount called
、mounted called
。表現出,在掛載組件後,info
被created
, beforeMount
, mounted
賦值,並渲染至屏幕上。可是本應在最開始就執行的beforeCreate
卻並無給info
賦值。
卸載組件時,由於程序運行太快,爲了方便觀察,特地爲beforeDestroy
和beforeDestroy
函數在最後添加了斷點。發現點擊卸載組價後,Vue在v-if=true
時會直接從文檔模型中卸載組件(此時組件已經不在document)。異步
控制檯輸出的內容很是具備表明性。
咱們能夠發現,控制檯按照建立、掛載、更新、銷燬依次打印出對應的鉤子函數。展開來看
在觸發beforeCreate
函數時,vue實例還還沒有初始化$data
,所以也就沒法給$data
賦值,也就很好的解釋了爲何在屏幕上,沒有渲染出beforeCreate called
。同時,由於還沒有被掛載,也就沒法獲取到$el
。
在觸發created
函數時,其實就代表,該組件已經被建立了。所以給info
賦值後,待組件掛載後,視圖也會渲染出created called
。
在觸發beforeMount
函數時,其實就代表,該組件即將被掛載。此時組建表現出的特性與created
保持一致。
在觸發mounted
函數時,其實就代表,該組件已經被掛載。所以給info
賦值後,待組件掛載後,視圖也會渲染出mounted called
,而且控制檯能夠獲取到$el
觸發beforeUpdate
與updated
,分別表示視圖更新先後,更新前$data
領先視圖,更新後,保持一致。在這兩個回調函數中,更改data時注意避免循環回調。
觸發beforeDestroy
與destroyed
,表示實例在銷燬先後,改變$data
,會觸發一次updated
,因在同一個函數中(下文會介紹)回調,故捏合在一塊兒說明。
名稱 | 觸發階段 | $data |
$el |
---|---|---|---|
beforeCreate | 組件建立前 | ✖ | ✖ |
created | 組件建立後 | ✔ | ✖ |
beforeMount | 組件掛載前 | ✔ | ✖ |
mounted | 組件掛載後 | ✔ | ✔ |
beforeUpdate | 組件更新前 | ✔ | ✔ |
updated | 組件更新後 | ✔ | ✔ |
beforeDestroy | 組件建立前 | ✔ | ✔ |
destroyed | 組件建立前 | ✔ | ✔ |
Vue生命週期函數在源碼文件/src/core/instance/init.js
中定義,並在/src/core/instance/init.js
、src/core/instance/lifecycle.js
、/src/core/observer/scheduler.js
三個文件中調用了全部的生命週期函數
當在特定的使其,須要調用生命週期鉤子時,源碼只需調用callHook函數,並傳入兩個參數,第一個爲vue實例,第二個爲鉤子名稱。以下
export function callHook (vm: Component, hook: string) { // #7573 disable dep collection when invoking lifecycle hooks pushTarget() const handlers = vm.$options[hook] if (handlers) { //? 這裏爲何是數組?在什麼狀況下,數組的索引會大於1? for (let i = 0, j = handlers.length; i < j; i++) { try { handlers[i].call(vm) } catch (e) { handleError(e, vm, `${hook} hook`) } } } if (vm._hasHookEvent) { vm.$emit('hook:' + hook) } popTarget() }
callHook在打包時,並無暴露在全局做用域。但咱們能夠根據Vue實例來手動調用生命週期函數。試着在掛在組件後在控制檯輸入vue.$children[0].$options['beforeCreate'][0].call(vue.$children[0])
,能夠發現組件的beforeCreate鉤子已經被觸發了。而且表示出了與本意相駁的特性。此時由於組件已經初始化,而且已經掛載,因此成功在控制檯打印出$el
與$data
,並在修改info
後成功觸發了beforeUpdate
與beforeUpdate
beforeCreate
與created
Vue會在/src/core/instance/init.js
中經過initMixin
函數對Vue實例進行進一步初始化操做。
export function initMixin (Vue: Class<Component>) { 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) // 定義$data initProvide(vm) // resolve provide after data/props callHook(vm, 'created') /* ... */ } }
能夠看出在執行callHook(vm, 'beforeCreate')
以前,Vue還還沒有初始化data,這也就解釋了,爲何在控制檯beforeCreate獲取到的$data
爲undefined
,而callHook(vm, 'created')
卻能夠,以及屏幕上爲何沒有打印出beforeCreate called
。
beforeMount
與mounted
Vue在/src/core/instance/lifecycle.js
中定義了mountComponent
函數,並在該函數內,調用了beforeMount
與mounted
。
export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { vm.$el = el // 組件掛載時 `el` 爲`undefined` callHook(vm, 'beforeMount') // 因此獲取到的`$el`爲`undefined` /* ... */ // 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 //! 挖個新坑 下節分享渲染watch。 通過渲染後,便可獲取`$el` new Watcher(vm, updateComponent, noop, null, 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 // 由於已經渲染,`$el`此時已經能夠成功獲取 callHook(vm, 'mounted') } return vm }
beforeUpdate
與updated
beforeUpdate
與updated
涉及到watcher,所以將會在之後的章節進行詳解。
beforeDestroy
與destroyed
Vue將卸載組件的方法直接定義在原型鏈上,所以能夠經過直接調用vm.$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 // 調起`destroyed`鉤子函數 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 } } }
vue.$children[0].$options['beforeCreate']
爲何是一個數組?updated called
?new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */)
這行代碼以後發生了什麼?beforeUpdate
背後實現原理。