這篇文章帶給大家的內容是關於Vue的生命週期及原始碼實現(程式碼) ,有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。
透過學習,我們已經學會了Vue的所有基本語法,包括:
1、{{Mustache}} 語法
2、v-if、v-else、v-else-if、v-show
3、v-for
4、v-bind
5、v-model
6.v-on
如果大家已經對這些語法牢記於心了,那麼請繼續往下看,如果大家對這些語法掌握的並不是很熟練的話,那麼希望大家再去回顧一下前面的內容。
這一章我們學習Vue的生命週期,我們先來看看Vue的生命週期的定義。
每個 Vue 實例在被創建時都要經過一系列的初始化過程——例如,需要設定資料監聽、編譯模板、將實例掛載到 DOM 並在資料變更時更新 DOM 等。同時在這個過程中也會執行一些叫做生命週期鉤子的函數,這給了使用者在不同階段加入自己的程式碼的機會。
這是Vue官網上提供的描述訊息,簡單來說就是:在Vue從創建實例到最終完全消亡的過程中,會執行一系列的方法,用於對應當前Vue的狀態,這些方法我們叫它:生命週期鉤子。我們來看看下面的生命週期圖示:
在上面的圖示中,共展示出來8個生命週期鉤子函數,這8個函數就描繪出來了Vue整個的運行週期。而截止到目前的Vue版本-2.5.16。 Vue的宣告週期鉤子總共為11個,除去剛才的8個之外,還有3個關於元件的生命週期鉤子。我們來看看所有的鉤子函數解釋,配合上面的圖示,可以更好地理解Vue的運行週期。
1、beforeCreate:在實例初始化之後,資料觀測 (data observer) 和 event/watcher 事件配置之前被呼叫。
2、created:在實例建立完成後立即被呼叫。在這一步,實例已完成以下的配置:資料觀測 (data observer),屬性和方法的運算,watch/event 事件回呼。然而,掛載階段還沒開始,$el
屬性目前不可見。
3、beforeMount:在掛載開始之前被呼叫:相關的 render 函數首次被呼叫。
4、mounted:el 被新建立的 vm.$el
替換,並掛載到實例上去之後呼叫該鉤子。如果root 實例掛載了一個文檔內元素,當mounted 被呼叫時vm.$el
也在文檔內(PS:注意mounted 不會承諾所有的子元件也都一起被掛載。如果你希望等到整個視圖都渲染完畢,可以用vm.$nextTick
替換掉mounted:)。 vm.$nextTick
會在後面的章節詳細講解,這裡大家需要知道有這個東西。
5、beforeUpdate:資料更新時調用,發生在虛擬 DOM 打補丁之前。這裡適合在更新之前存取現有的 DOM,例如手動移除已新增的事件監聽器。
6、updated:由於資料變更導致的虛擬 DOM 重新渲染和打補丁,在這之後會呼叫該鉤子。當這個鉤子被呼叫時,元件 DOM 已經更新,所以你現在可以執行依賴 DOM 的操作。然而在大多數情況下,你應該避免在此期間更改狀態。如果要相應狀態改變,通常最好使用計算屬性或 watcher 取代(PS:計算屬性與watcher會在後面的章節進行介紹)。
7、activated:keep-alive 元件啟動時調用(PS:與元件相關,關於keep-alive會在講解元件的時候為大家介紹)。
8、deactivated:keep-alive 元件停用時呼叫(PS:與元件相關,關於keep-alive會在講解元件的時候為大家介紹)。
9、beforeDestroy:實例銷毀之前呼叫。在這一步,實例仍然完全可用。
10、destroyed:Vue 實例銷毀後呼叫。呼叫後,Vue 實例所指示的所有東西都會解綁定,所有的事件監聽器會被移除,所有的子實例也會被銷毀。
11、errorCaptured(2.5.0 新增):當捕獲一個來自子孫元件的錯誤時被呼叫。此鉤子會收到三個參數:錯誤物件、發生錯誤的元件實例以及一個包含錯誤來源資訊的字串。此鉤子可以傳回 false 以防止該錯誤繼續向上傳播。
這就是Vue(2.5.16)中所有的生命週期鉤子,為了更方便大家的理解,我們來看一下,在Vue的程式碼中,從創建到銷毀是如何在實現的。大家可以點這裡下載Vue的最新程式碼。
我們先大致來看Vue原始碼的基礎架構
。
. ├── BACKERS.md ├── LICENSE ├── README.md├── benchmarks ├── dist ├── examples ├── flow ├── node_modules ├── package.json├── packages ├── scripts ├── src ├── test ├── types └── yarn.lock
這是下載下來程式碼之後的一級目錄,dist資料夾下為Vue編譯之後的程式碼,我們平常引入的Vue.js檔案都在這裡
, Vue使用了flow作為JavaScript靜態類型檢查工具,相關的程式碼都在flow資料夾下面
,scripts資料夾下面是程式碼建置的相關配置,Vue主要使用Rollup進行的程式碼建置
, src資料夾下面就是所有Vue的原始碼
。我們這裡不對其他的內容進行過多的描述,還是專注於我們的主題,Vue的宣告週期程式碼是如何實作,我們看一下src
資料夾。
. ├── compiler :Vue编译相关 ├── core :Vue的核心代码 ├── platforms :web/weex平台支持,入口文件 ├── server :服务端 ├── sfc :解析.vue文件 └── shared :公共代码
這是我們src
資料夾下的目錄結構,而我們Vue產生的地方就在/src/core/instance/index. js
中。
function Vue (options) { if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue) ) { warn('Vue is a constructor and should be called with the `new` keyword') } this._init(options) }
我們可以看到:Vue是一個方法,是使用Function來實現的建構函數,所以我們只能透過 new 的方式來去建立Vue的實例。 接著透過Vue實例
的_init
方法來進行Vue的初始化。 _init
是Vue透過prototype
來實現的一個原型屬性
。我們來看看他的_init
方法實作。
在/src/core/instance/init.js
資料夾下,Vue實作了_init
方法
Vue.prototype._init = function (options?: Object) { ... // expose real self 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') ... if (vm.$options.el) { vm.$mount(vm.$options.el) } }
我主要看與它生命週期有關的程式碼,我們可以看到,Vue先呼叫了initLifecycle(vm)、initEvents(vm)、initRender(vm)
這三個方法,用於初始化生命週期、事件、渲染函數
,這些過程發生在Vue初始化的過程(_init方法)中
,並在呼叫beforeCreate鉤子
之前。
然後Vue透過callHook (vm: Component, hook: string)
方法來去呼叫鉤子函數(hook)
,它接收vm(Vue實例物件),hook(鉤子函數名稱)
來去執行生命週期函數
。在Vue中幾乎所有的鉤子(errorCaptured
除外)函數執行都是透過callHook (vm: Component, hook: string)
來呼叫的。讓我們來看看callHook
的程式碼,在/src/core/instance/lifecycle.js
下:
export function callHook (vm: Component, hook: string) { // #7573 disable dep collection when invoking lifecycle hooks pushTarget() const handlers = vm.$options[hook] 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) } popTarget() }
它的邏輯也非常簡單,根據傳入的hook
從實例中拿到對應的回呼函數陣列(在/packages/vue-template-compiler/browser.js
下的LIFECYCLE_HOOKS
),然後便利執行。
然後在初始化生命週期、事件、渲染函數
之後呼叫了beforeCreate鉤子
,在這個時候:我們還沒辦法取得到 data、props
等資料。
在呼叫了beforeCreate鉤子
之後,Vue呼叫了initInjections(vm)、initState(vm)、initProvide(vm)
這三個方法用來初始化data、props、watcher
等等,在這些初始化執行完成之後,呼叫了created鉤子函數
,在這個時候:我們已經可以取得到data、props
等資料了,但是Vue並沒有開始渲染DOM
,所以我們還不能夠訪問DOM(PS:我們可以透過vm.$nextTick
來訪問,在後面的章節我們會詳細講解)。
在呼叫了created鉤子
之後,Vue開始進行DOM的掛載,執行vm.$mount(vm.$options. el)
,在Vue中DOM的掛載就是透過Vue.prototype.$mount
這個原型方法來去實現的。 Vue.prototype.$mount
原型方法的宣告是在/src/platforms/web/entry-runtime-with-compiler.js
,我們來看看這個程式碼的實作:
const mount = Vue.prototype.$mount Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && query(el) /* istanbul ignore if */ if (el === document.body || el === document.documentElement) { process.env.NODE_ENV !== 'production' && warn( `Do not mount Vue to <html> or <body> - mount to normal elements instead.` ) return this } const options = this.$options // resolve template/el and convert to render function if (!options.render) { let template = options.template if (template) { if (typeof template === 'string') { if (template.charAt(0) === '#') { template = idToTemplate(template) /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && !template) { warn( `Template element not found or is empty: ${options.template}`, this ) } } } else if (template.nodeType) { template = template.innerHTML } else { if (process.env.NODE_ENV !== 'production') { warn('invalid template option:' + template, this) } return this } } else if (el) { template = getOuterHTML(el) } if (template) { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile') } const { render, staticRenderFns } = compileToFunctions(template, { shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) options.render = render options.staticRenderFns = staticRenderFns /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile end') measure(`vue ${this._name} compile`, 'compile', 'compile end') } } } return mount.call(this, el, hydrating) } /** * Get outerHTML of elements, taking care * of SVG elements in IE as well. */function getOuterHTML (el: Element): string { if (el.outerHTML) { return el.outerHTML } else { const container = document.createElement('p') container.appendChild(el.cloneNode(true)) return container.innerHTML } }
這部分程式碼的主要作用:就是進行template模板的解析
#。從上面的程式碼可以看出,el不允許被掛載到body
和html
這樣的根標籤上面。 接著判斷是否有render函數-> if (!options.render) {...}
,然後判斷有沒有template
,template可以是string類型的id
、DOM節點
。沒有的話則解析el
作為template
。由上面的程式碼可以看出我們無論是使用單一檔案元件(.Vue)
或透過el、template屬性
,它最終都會通過render
函數的形式來進行整個模板的解析。
由我們的圖示可以看出模板解析完成之後,會呼叫beforeMount鉤子
,那麼這個beforeMount鉤子
是在哪裡被呼叫的呢?我們接著往下看。 $mount
原型方法有一個可重複使用的設計,在/src/platforms/web/runtime/index.js
下,有這麼一段程式碼
// public mount method Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) }
这是一个公共的挂载方法,目的是为了被runtime only
版本的Vue直接使用,它调用了mountComponent
方法。我们看一下mountComponent
方法的实现,实现在/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 if (process.env.NODE_ENV !== 'production') { /* istanbul ignore if */ if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') || vm.$options.el || el) { warn( 'You are using the runtime-only build of Vue where the template ' + 'compiler is not available. Either pre-compile the templates into ' + 'render functions, or use the compiler-included build.', vm ) } else { warn( 'Failed to mount component: template or render function not defined.', vm ) } } } callHook(vm, 'beforeMount') let updateComponent /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { updateComponent = () => { const name = vm._name const id = vm._uid const startTag = `vue-perf-start:${id}` const endTag = `vue-perf-end:${id}` mark(startTag) const vnode = vm._render() mark(endTag) measure(`vue ${name} render`, startTag, endTag) mark(startTag) vm._update(vnode, hydrating) mark(endTag) measure(`vue ${name} patch`, startTag, endTag) } } else { updateComponent = () => { vm._update(vm._render(), hydrating) } } // 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 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 callHook(vm, 'mounted') } return vm }
由上面的代码可以看出在执行vm._render()
之前,调用了callHook(vm, 'beforeMount')
,这个时候相关的 render 函数首次被调用,调用完成之后,执行了callHook(vm, 'mounted')
方法,标记着el 被新创建的 vm.$el 替换,并被挂载到实例上。
然后就进入了我们页面正常交互的时间
,也就是beforeUpdate
和updated
这两个回调钩子的执行时机。这两个钩子函数是在数据更新的时候进行回调的函数,Vue在/src/core/instance/lifecycle.js
文件下有一个_update
的原型声明:
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { const vm: Component = this if (vm._isMounted) { callHook(vm, 'beforeUpdate') } const prevEl = vm.$el const prevVnode = vm._vnode const prevActiveInstance = activeInstance activeInstance = vm vm._vnode = vnode // Vue.prototype.__patch__ is injected in entry points // based on the rendering backend used. if (!prevVnode) { // initial render vm.$el = vm.__patch__( vm.$el, vnode, hydrating, false /* removeOnly */, vm.$options._parentElm, vm.$options._refElm ) // no need for the ref nodes after initial patch // this prevents keeping a detached DOM tree in memory (#5851) vm.$options._parentElm = vm.$options._refElm = null } else { // updates vm.$el = vm.__patch__(prevVnode, vnode) } activeInstance = prevActiveInstance // update __vue__ reference if (prevEl) { prevEl.__vue__ = null } 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. }
我们可以看到在如果_isMounted
为ture
的话(DOM已经被挂载)则会调用callHook(vm, 'beforeUpdate')
方法,然后会对虚拟DOM进行重新渲染。然后在/src/core/observer/scheduler.js
下的flushSchedulerQueue()
函数中渲染DOM,在渲染完成调用callHook(vm, 'updated')
,代码如下:。
/** * Flush both queues and run the watchers. */function flushSchedulerQueue () { ... 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) { callHook(vm, 'updated') } } }
当Vue实例需要进行销毁的时候回调beforeDestroy 、destroyed
这两个函数钩子,它们的实现是在/src/core/instance/lifecycle.js
下的Vue.prototype.$destroy
中:
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) // 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 } }
在$destroy
这个原型函数中,执行了Vue的销毁操作
,我们可以看到在执行销毁操作之前调用了callHook(vm, 'beforeDestroy')
,然后执行了一系列的销毁操作,包括删除掉所有的自身(self)、_watcher、数据引用
等等,删除完成之后调用callHook(vm, 'destroyed')
。
截止到这里,整个Vue生命周期图示中的所有生命周期钩子
都已经被执行完成了。那么剩下的activated、deactivated、errorCaptured
这三个钩子函数是在何时被执行的呢?我们知道这三个函数都是针对于组件(component)
的钩子函数。其中activated、deactivated
这两个钩子函数分别是在keep-alive 组件激活和停用
之后回调的,它们不牵扯到整个Vue的生命周期之中,activated、deactivated
这两个钩子函数的实现代码都在/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') } }
而对于errorCaptured
来说,它是在2.5.0之后新增的一个钩子函数,它的代码在/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 { const capture = hooks[i].call(cur, err, vm, info) === false if (capture) return } catch (e) { globalHandleError(e, cur, 'errorCaptured hook') } } } } } globalHandleError(err, vm, info) }function globalHandleError (err, vm, info) { if (config.errorHandler) { try { return config.errorHandler.call(null, err, vm, info) } catch (e) { logError(e, null, 'config.errorHandler') } } logError(err, vm, info) }function logError (err, vm, info) { if (process.env.NODE_ENV !== 'production') { warn(`Error in ${info}: "${err.toString()}"`, vm) } /* istanbul ignore else */ if ((inBrowser || inWeex) && typeof console !== 'undefined') { console.error(err) } else { throw err } }
他是唯一一个没有通过callHook
方法来执行的钩子函数,而是直接通过遍历cur(vm).$options.errorCaptured
,来执行config.errorHandler.call(null, err, vm, info)
的钩子函数。整个逻辑的结构与callHook
使非常类似的。
截止到目前Vue中所有的生命周期钩子我们都已经介绍完成了,其中涉及到了一些源码的基础,是因为我觉得配合源码来一起看的话,会对整个Vue的运行过程有个更好的理解。大家一定要下载下来Vue的源代码,对照着我们的讲解来走一遍这个流程。
相关推荐:
以上是Vue的生命週期及原始碼實作(程式碼)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

JavaScript在現實世界中的應用包括服務器端編程、移動應用開發和物聯網控制:1.通過Node.js實現服務器端編程,適用於高並發請求處理。 2.通過ReactNative進行移動應用開發,支持跨平台部署。 3.通過Johnny-Five庫用於物聯網設備控制,適用於硬件交互。

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。

Python更适合数据科学和机器学习,JavaScript更适合前端和全栈开发。1.Python以简洁语法和丰富库生态著称,适用于数据分析和Web开发。2.JavaScript是前端开发核心,Node.js支持服务器端编程,适用于全栈开发。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

SAP NetWeaver Server Adapter for Eclipse
將Eclipse與SAP NetWeaver應用伺服器整合。

DVWA
Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

SublimeText3 英文版
推薦:為Win版本,支援程式碼提示!

記事本++7.3.1
好用且免費的程式碼編輯器

Atom編輯器mac版下載
最受歡迎的的開源編輯器