春節繼續寫博客~加油!
此次來學習一下Vue的生命週期,看看生命週期是怎麼回事。html
生命週期主要就是在源碼某個時間點執行這個 callHook
方法來調用 vm.$options
的生命週期鉤子方法(若是定義了生命週期鉤子方法的話)。
咱們來看看 callHook 代碼:前端
export function callHook (vm: Component, hook: string) { const handlers = vm.$options[hook] // 獲取Vue選項中的生命週期鉤子函數 if (handlers) { 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) } }
好比觸發 mounted
鉤子的方法:vue
callHook(vm, 'mounted')
先上一張圖看下Vue的生命週期,咱們能夠在相應的生命週期中定義一些事件。node
先看看這兩個方法調用的時間。git
beforeCreate
在實例初始化以後,數據觀測 (data observer) 和 event/watcher 事件配置以前被調用。
created
在實例建立完成後被當即調用。在這一步,實例已完成如下的配置:數據觀測 (data observer),屬性和方法的運算,watch/event 事件回調。然而,掛載階段還沒開始,$el 屬性目前不可見。
具體代碼以下github
// src/core/instance/init.js Vue.prototype._init = function (options?: Object) { …… initLifecycle(vm) // 初始化生命週期 initEvents(vm) // 初始化事件 initRender(vm) // 初始化渲染 callHook(vm, 'beforeCreate') initInjections(vm) // 初始化Inject initState(vm) // 初始化數據 initProvide(vm) // 初始化Provide callHook(vm, 'created') …… if (vm.$options.el) { vm.$mount(vm.$options.el) // 若是有el屬性,將內容掛載到el中去。 } }
beforeMount
在掛載開始以前被調用:相關的 render 函數首次被調用。該鉤子在服務器端渲染期間不被調用。
mounted
el 被新建立的 vm.$el 替換,並掛載到實例上去以後調用該鉤子。若是 root 實例掛載了一個文檔內元素,當 mounted 被調用時 vm.$el 也在文檔內。
貼出代碼邏輯web
// src/core/instance/lifecycle.js // 掛載組件的方法 export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { vm.$el = el if (!vm.$options.render) { vm.$options.render = createEmptyVNode } callHook(vm, 'beforeMount') let updateComponent updateComponent = () => { vm._update(vm._render(), hydrating) } vm._watcher = new Watcher(vm, updateComponent, noop) hydrating = false if (vm.$vnode == null) { vm._isMounted = true callHook(vm, 'mounted') } return vm }
那麼這個 mountComponent
在哪裏用了呢?就是在Vue的 $mount 方法中使用。數組
// src/platforms/web/runtime/index.js Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) }
最後會在Vue初始化的時候,判斷是否有 el,若是有則執行 $mount 方法。服務器
// src/core/instance/init.js if (vm.$options.el) { vm.$mount(vm.$options.el) // 若是有el屬性,將內容掛載到el中去。 }
至今生命週期邏輯應該是 beforeCreate - created - beforeMount -mounted前端工程師
beforeUpdate
數據更新時調用,發生在虛擬 DOM 打補丁以前。這裏適合在更新以前訪問現有的 DOM,好比手動移除已添加的事件監聽器。
updated
因爲數據更改致使的虛擬 DOM 從新渲染和打補丁,在這以後會調用該鉤子。
找代碼邏輯~ beforeUpdate 和 updated 在兩個地方調用。
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { const vm: Component = this // 若是是已經掛載的,就觸發beforeUpdate方法。 if (vm._isMounted) { callHook(vm, 'beforeUpdate') } …… // updated hook is called by the scheduler to ensure that children are // updated in a parent's updated hook. }
在執行 _update
方法的時候,若是 DOM 已經掛載了,則調用 beforeUpdate
方法。
在 _update 方法的最後做者也注視了調用 updated hook 的位置:updated
鉤子由 scheduler
調用來確保子組件在一個父組件的 update
鉤子中。
咱們找到 scheduler
,發現有個 callUpdateHooks
方法,該方法遍歷了 watcher
數組。
// src/core/observer/scheduler.js function callUpdatedHooks (queue) { let i = queue.length while (i--) { const watcher = queue[i] const vm = watcher.vm if (vm._watcher === watcher && vm._isMounted) { callHook(vm, 'updated') } } }
這個 callUpdatedHooks
在 flushSchedulerQueue
方法中調用。
/** * 刷新隊列並運行watcher */ function flushSchedulerQueue () { flushing = true let watcher, id queue.sort((a, b) => a.id - b.id) for (index = 0; index < queue.length; index++) { watcher = queue[index] id = watcher.id has[id] = null watcher.run() } const activatedQueue = activatedChildren.slice() const updatedQueue = queue.slice() resetSchedulerState() // 調用組件的updated和activated生命週期 callActivatedHooks(activatedQueue) callUpdatedHooks(updatedQueue) }
繼續找下去
export function queueWatcher (watcher: Watcher) { const id = watcher.id if (has[id] == null) { has[id] = true // 此參數用於判斷watcher的ID是否存在 …… if (!waiting) { waiting = true nextTick(flushSchedulerQueue) } } }
最終在 watcher.js
找到 update
方法:
// src/core/observer/watcher.js update () { // lazy 懶加載 // sync 組件數據雙向改變 if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this) // 排隊watcher } }
等因而隊列執行完 Watcher 數組的 update
方法後調用了 updated
鉤子函數。
beforeDestroy
實例銷燬以前調用。在這一步,實例仍然徹底可用。該鉤子在服務器端渲染期間不被調用。
destroyed
Vue 實例銷燬後調用。調用後,Vue 實例指示的全部東西都會解綁定,全部的事件監聽器會被移除,全部的子實例也會被銷燬。該鉤子在服務器端渲染期間不被調用。
看代碼~
// src/core/instance/lifecycle.js // 銷燬方法 Vue.prototype.$destroy = function () { const vm: Component = this if (vm._isBeingDestroyed) { // 已經被銷燬 return } 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) // 觸發 destroyed 鉤子 callHook(vm, 'destroyed') // turn off all instance listeners. vm.$off() // remove __vue__ reference if (vm.$el) { vm.$el.__vue__ = null } }
這是一個銷燬 Vue 實例的過程,將各類配置清空和移除。
activated
keep-alive 組件激活時調用。
deactivated
keep-alive 組件停用時調用。
找到實現代碼的地方
// src/core/instance/lifecycle.js export function activateChildComponent (vm: Component, direct?: boolean) { if (direct) { vm._directInactive = false if (isInInactiveTree(vm)) { return } } else if (vm._directInactive) { return } if (vm._inactive || vm._inactive === null) { vm._inactive = false for (let i = 0; i < vm.$children.length; i++) { activateChildComponent(vm.$children[i]) } callHook(vm, 'activated') } } export function deactivateChildComponent (vm: Component, direct?: boolean) { if (direct) { vm._directInactive = true if (isInInactiveTree(vm)) { return } } if (!vm._inactive) { vm._inactive = true for (let i = 0; i < vm.$children.length; i++) { deactivateChildComponent(vm.$children[i]) } callHook(vm, 'deactivated') } }
以上兩個方法關鍵就是修改了 vm._inactive
的值,而且鄉下遍歷子組件,最後觸發鉤子方法。
當捕獲一個來自子孫組件的錯誤時被調用。此鉤子會收到三個參數:錯誤對象、發生錯誤的組件實例以及一個包含錯誤來源信息的字符串。此鉤子能夠返回 false 以阻止該錯誤繼續向上傳播。
這是 2.5 以上版本有的一個鉤子,用於處理錯誤。
// src/core/util/error.js export function handleError (err: Error, vm: any, info: string) { if (vm) { let cur = vm // 向上冒泡遍歷 while ((cur = cur.$parent)) { // 獲取鉤子函數 const hooks = cur.$options.errorCaptured if (hooks) { for (let i = 0; i < hooks.length; i++) { try { // 執行 errorCaptured 鉤子函數 const capture = hooks[i].call(cur, err, vm, info) === false if (capture) return } catch (e) { globalHandleError(e, cur, 'errorCaptured hook') } } } } } globalHandleError(err, vm, info) }
代碼很簡單,看代碼便可~
除了生命週期鉤子外,vue還提供了生命週期方法來直接調用。
若是 Vue 實例在實例化時沒有收到 el 選項,則它處於「未掛載」狀態,沒有關聯的 DOM 元素。可使用 vm.$mount() 手動地掛載一個未掛載的實例。
若是沒有提供 elementOrSelector 參數,模板將被渲染爲文檔以外的的元素,而且你必須使用原生 DOM API 把它插入文檔中。
這個方法返回實例自身,於是能夠鏈式調用其它實例方法。
const mount = Vue.prototype.$mount Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && query(el) if (el === document.body || el === document.documentElement) { return this } const options = this.$options // resolve template/el and convert to render function if (!options.render) { // 獲取template let template = options.template if (template) { if (typeof template === 'string') { if (template.charAt(0) === '#') { template = idToTemplate(template) } } else if (template.nodeType) { template = template.innerHTML } else { return this } } else if (el) { template = getOuterHTML(el) } // 編譯template if (template) { const { render, staticRenderFns } = compileToFunctions(template, { shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) options.render = render options.staticRenderFns = staticRenderFns } } // 執行 $mount 方法 return mount.call(this, el, hydrating) }
其實很簡單,先獲取html代碼,而後執行 compileToFunctions
方法執行編譯過程(具體編譯過程在學習Render的時候再說)。
迫使 Vue 實例從新渲染。注意它僅僅影響實例自己和插入插槽內容的子組件,而不是全部子組件。
Vue.prototype.$forceUpdate = function () { var vm = this; if (vm._watcher) { vm._watcher.update(); } };
這是強制更新方法,執行了 vm._watcher.update()
方法。
將回調延遲到下次 DOM 更新循環以後執行。在修改數據以後當即使用它,而後等待 DOM 更新。它跟全局方法 Vue.nextTick 同樣,不一樣的是回調的 this 自動綁定到調用它的實例上。
找了找 vm.$nextTick
的代碼
// src/core/instance/render.js Vue.prototype.$nextTick = function (fn: Function) { return nextTick(fn, this) }
找到這個 nextTick
方法:
// src/core/util/next-tick.js export function nextTick (cb?: Function, ctx?: Object) { let _resolve callbacks.push(() => { if (cb) { try { cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) if (!pending) { pending = true if (useMacroTask) { macroTimerFunc() } else { microTimerFunc() } } // $flow-disable-line if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } }
具體功能邏輯等學習完 render
再更新……
徹底銷燬一個實例。清理它與其它實例的鏈接,解綁它的所有指令及事件監聽器。
觸發 beforeDestroy 和 destroyed 的鉤子。
關於$destroy 咱們以前再說 destroyed 鉤子的時候提到過了,這裏就再也不贅述。
Vue.prototype.$destroy = function () { …… }
首先說下過年博客計劃,過年學習Vue各個模塊的源碼,併發布相應博客。另外還會發布一些前端知識的整理,便於下個月找工做~
而後,小結下本身看源碼的一些小技巧:
OK,今天就這麼多~ 明天去學習下Vue的事件源碼!加油!明天見!
鑑於前端知識碎片化嚴重,我但願可以系統化的整理出一套關於Vue的學習系列博客。
本文源碼已收入到GitHub中,以供參考,固然能留下一個star更好啦^-^。
https://github.com/violetjack/VueStudyDemos
VioletJack,高效學習前端工程師,喜歡研究提升效率的方法,也專一於Vue前端相關知識的學習、整理。
歡迎關注、點贊、評論留言~我將持續產出Vue相關優質內容。
新浪微博: http://weibo.com/u/2640909603
掘金:https://gold.xitu.io/user/571...
CSDN: http://blog.csdn.net/violetja...
簡書: http://www.jianshu.com/users/...
Github: https://github.com/violetjack