vue中created、watch和computed的執(zhí)行順序詳解
前言
面試題:
vue
中created
、watch(immediate: true)
和computed
的執(zhí)行順序是啥?
先看個(gè)簡單的例子:
// main.js import Vue from "vue"; new Vue({ el: "#app", template: `<div> <div>{{computedCount}}</div> </div>`, data() { return { count: 1, } }, watch: { count: { handler() { console.log('watch'); }, immediate: true, } }, computed: { computedCount() { console.log('computed'); return this.count + 1; } }, created() { console.log('created'); }, });
當(dāng)前例子的執(zhí)行順序?yàn)椋?code>watch --> created
--> computed
。
為什么?
在new Vue
的實(shí)例化過程中,會(huì)執(zhí)行初始化方法this._init
,其中有代碼:
Vue.prototype._init = function (options) { // ... initState(vm); // ... callHook(vm, 'created'); // ... if (vm.$options.el) { vm.$mount(vm.$options.el); } } function initState (vm) { vm._watchers = []; var opts = vm.$options; if (opts.props) { initProps(vm, opts.props); } if (opts.methods) { initMethods(vm, opts.methods); } if (opts.data) { initData(vm); } else { observe(vm._data = {}, true /* asRootData */); } if (opts.computed) { initComputed(vm, opts.computed); } if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch); } }
猛一看代碼,是不是發(fā)現(xiàn)先執(zhí)行的initComputed(vm, opts.computed)
,然后執(zhí)行initWatch(vm, opts.watch)
,再執(zhí)行callHook(vm, 'created')
,那為啥不是computed
--> watch
--> created
呢?
不要著急,聽我娓娓道來。
1、關(guān)于initComputed
const computedWatcherOptions = { lazy: true } function initComputed (vm: Component, computed: Object) { // $flow-disable-line const watchers = vm._computedWatchers = Object.create(null) // computed properties are just getters during SSR const isSSR = isServerRendering() for (const key in computed) { const userDef = computed[key] const getter = typeof userDef === 'function' ? userDef : userDef.get // ... if (!isSSR) { // create internal watcher for the computed property. watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ) } // component-defined computed properties are already defined on the // component prototype. We only need to define computed properties defined // at instantiation here. if (!(key in vm)) { defineComputed(vm, key, userDef) } else if (process.env.NODE_ENV !== 'production') { // ... } } }
在通過initComputed
初始化計(jì)算屬性的時(shí)候,通過遍歷的方式去處理當(dāng)前組件中的computed
。首先,在進(jìn)行計(jì)算屬性實(shí)例化的時(shí)候,將{ lazy: true }
作為參數(shù)傳入,并且實(shí)例化的Watcher
中的getter
就是當(dāng)前例子中的computedCount
函數(shù);其次,通過defineComputed(vm, key, userDef)
的方式在當(dāng)前組件實(shí)例vm
上為key
進(jìn)行userDef
的處理。具體為:
export function defineComputed ( target: any, key: string, userDef: Object | Function ) { const shouldCache = !isServerRendering() if (typeof userDef === 'function') { sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) : createGetterInvoker(userDef) sharedPropertyDefinition.set = noop } // ... Object.defineProperty(target, key, sharedPropertyDefinition) } function createComputedGetter (key) { return function computedGetter () { const watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) { if (watcher.dirty) { watcher.evaluate() } if (Dep.target) { watcher.depend() } return watcher.value } } }
從以上可以看出,這里通過Object.defineProperty(target, key, sharedPropertyDefinition)
的方式,將函數(shù)computedGetter
作為get
函數(shù),只有當(dāng)對(duì)key
進(jìn)行訪問的時(shí)候,才會(huì)觸發(fā)其內(nèi)部的邏輯。內(nèi)部邏輯watcher.evaluate()
為:
evaluate () { this.value = this.get() this.dirty = false }
get
中有主要邏輯:
value = this.getter.call(vm, vm)
這里的this.getter
就是當(dāng)前例子中的:
computedCount() { console.log('computed'); return this.count + 1; }
也就是說,只有當(dāng)獲取computedCount
的時(shí)候才會(huì)觸發(fā)computed
的計(jì)算,也就是在進(jìn)行vm.$mount(vm.$options.el)
階段才會(huì)執(zhí)行到console.log('computed')
。
2、關(guān)于initWatch
function initWatch (vm: Component, watch: Object) { for (const key in watch) { const handler = watch[key] if (Array.isArray(handler)) { for (let i = 0; i < handler.length; i++) { createWatcher(vm, key, handler[i]) } } else { createWatcher(vm, key, handler) } } } function createWatcher ( vm: Component, expOrFn: string | Function, handler: any, options?: Object ) { if (isPlainObject(handler)) { options = handler handler = handler.handler } if (typeof handler === 'string') { handler = vm[handler] } return vm.$watch(expOrFn, handler, options) }
在通過initWatch
初始化偵聽器的時(shí)候,如果watch
為數(shù)組,則遍歷執(zhí)行createWatcher
,否則直接執(zhí)行createWatcher
。如果handler
是對(duì)象或者字符串時(shí),將其進(jìn)行處理,最終作為參數(shù)傳入vm.$watch
中去,具體為:
Vue.prototype.$watch = function ( expOrFn: string | Function, cb: any, options?: Object ): Function { const vm: Component = this if (isPlainObject(cb)) { return createWatcher(vm, expOrFn, cb, options) } options = options || {} options.user = true const watcher = new Watcher(vm, expOrFn, cb, options) if (options.immediate) { try { cb.call(vm, watcher.value) } catch (error) { handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`) } } return function unwatchFn () { watcher.teardown() } }
這里獲取到的options
中會(huì)有immediate: true
的鍵值,同時(shí)通過options.user = true
設(shè)置user
為true
,再將其作為參數(shù)傳入去進(jìn)行Watcher
的實(shí)例化。
當(dāng)前例子中options.immediate
為true
,所以會(huì)執(zhí)行cb.call(vm, watcher.value)
,也就是以vm
為主體,立刻執(zhí)行cb
。當(dāng)前例子中cb
就是handler
:
handler() { console.log('watch'); },
這里就解釋了當(dāng)前例子中console.log('watch')
是最先執(zhí)行的。
然后,執(zhí)行完initComputed
和initWatch
以后,就會(huì)通過callHook(vm, 'created')
執(zhí)行到生命周期中的console.log('created')
了。
最后通過vm.$mount(vm.$options.el)
進(jìn)行頁面渲染的時(shí)候,會(huì)先去創(chuàng)建vNode
,這時(shí)就需要獲取到computedCount
的值,進(jìn)而觸發(fā)其get
函數(shù)的后續(xù)邏輯,最終執(zhí)行到console.log('computed')
。
附:為什么vue中的watch在mounted之后執(zhí)行
首先,在調(diào)用mounted的時(shí)候,會(huì)進(jìn)入到defineReactive函數(shù),然后調(diào)用函數(shù)里面的set方法,將mounted中賦的新的值給傳遞過去,并通過調(diào)用dep.notify( )把消息發(fā)送給訂閱者,繼而更新訂閱者的列表
后面才開始渲染watch進(jìn)入Watcher的class
總結(jié)
關(guān)于
vue
中created
和watch
的執(zhí)行順序相對(duì)比較簡單,而其中computed
是通過Object.defineProperty
為當(dāng)前vm
進(jìn)行定義,再到后續(xù)創(chuàng)建vNode
階段才去觸發(fā)執(zhí)行其get
函數(shù),最終執(zhí)行到計(jì)算屬性computed
對(duì)應(yīng)的邏輯。
到此這篇關(guān)于vue中created、watch和computed執(zhí)行順序詳解的文章就介紹到這了,更多相關(guān)vue created、watch和computed執(zhí)行順序內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vant-ui組件庫中如何修改NavBar導(dǎo)航欄的樣式
這篇文章主要介紹了vant-ui組件庫中如何修改NavBar導(dǎo)航欄的樣式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08vue項(xiàng)目中的webpack-dev-sever配置方法
下面小編就為大家分享一篇vue項(xiàng)目中的webpack-dev-sever配置方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2017-12-12使用element ui中el-table-column進(jìn)行自定義校驗(yàn)
這篇文章主要介紹了使用element ui中el-table-column進(jìn)行自定義校驗(yàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08Vue實(shí)現(xiàn)點(diǎn)擊顯示不同圖片的效果
這篇文章主要為大家詳細(xì)介紹了Vue實(shí)現(xiàn)點(diǎn)擊顯示不同圖片的效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-08-08vue項(xiàng)目中vue.config.js文件詳解
vue.config.js?是一個(gè)可選的配置文件,如果項(xiàng)目的?(和?package.json?同級(jí)的)?根目錄中存在這個(gè)文件,那么它會(huì)被?@vue/cli-service?自動(dòng)加載,這篇文章主要介紹了vue項(xiàng)目中vue.config.js文件的介紹,需要的朋友可以參考下2024-02-02