vue中created、watch和computed的執(zhí)行順序詳解
前言
面試題:
vue中created、watch(immediate: true)和computed的執(zhí)行順序是啥?
先看個簡單的例子:
// 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');
},
});當前例子的執(zhí)行順序為:watch --> created --> computed。
為什么?
在new Vue的實例化過程中,會執(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初始化計算屬性的時候,通過遍歷的方式去處理當前組件中的computed。首先,在進行計算屬性實例化的時候,將{ lazy: true }作為參數(shù)傳入,并且實例化的Watcher中的getter就是當前例子中的computedCount函數(shù);其次,通過defineComputed(vm, key, userDef)的方式在當前組件實例vm上為key進行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ù),只有當對key進行訪問的時候,才會觸發(fā)其內(nèi)部的邏輯。內(nèi)部邏輯watcher.evaluate()為:
evaluate () {
this.value = this.get()
this.dirty = false
}get中有主要邏輯:
value = this.getter.call(vm, vm)
這里的this.getter就是當前例子中的:
computedCount() {
console.log('computed');
return this.count + 1;
}也就是說,只有當獲取computedCount的時候才會觸發(fā)computed的計算,也就是在進行vm.$mount(vm.$options.el)階段才會執(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初始化偵聽器的時候,如果watch為數(shù)組,則遍歷執(zhí)行createWatcher,否則直接執(zhí)行createWatcher。如果handler是對象或者字符串時,將其進行處理,最終作為參數(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中會有immediate: true的鍵值,同時通過options.user = true設(shè)置user為true,再將其作為參數(shù)傳入去進行Watcher的實例化。
當前例子中options.immediate為true,所以會執(zhí)行cb.call(vm, watcher.value),也就是以vm為主體,立刻執(zhí)行cb。當前例子中cb就是handler:
handler() {
console.log('watch');
},這里就解釋了當前例子中console.log('watch')是最先執(zhí)行的。
然后,執(zhí)行完initComputed和initWatch以后,就會通過callHook(vm, 'created')執(zhí)行到生命周期中的console.log('created')了。
最后通過vm.$mount(vm.$options.el)進行頁面渲染的時候,會先去創(chuàng)建vNode,這時就需要獲取到computedCount的值,進而觸發(fā)其get函數(shù)的后續(xù)邏輯,最終執(zhí)行到console.log('computed')。
附:為什么vue中的watch在mounted之后執(zhí)行
首先,在調(diào)用mounted的時候,會進入到defineReactive函數(shù),然后調(diào)用函數(shù)里面的set方法,將mounted中賦的新的值給傳遞過去,并通過調(diào)用dep.notify( )把消息發(fā)送給訂閱者,繼而更新訂閱者的列表
后面才開始渲染watch進入Watcher的class
總結(jié)
關(guān)于
vue中created和watch的執(zhí)行順序相對比較簡單,而其中computed是通過Object.defineProperty為當前vm進行定義,再到后續(xù)創(chuàng)建vNode階段才去觸發(fā)執(zhí)行其get函數(shù),最終執(zhí)行到計算屬性computed對應(yīng)的邏輯。
到此這篇關(guān)于vue中created、watch和computed執(zhí)行順序詳解的文章就介紹到這了,更多相關(guān)vue created、watch和computed執(zhí)行順序內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vant-ui組件庫中如何修改NavBar導(dǎo)航欄的樣式
這篇文章主要介紹了vant-ui組件庫中如何修改NavBar導(dǎo)航欄的樣式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-08-08
使用element ui中el-table-column進行自定義校驗
這篇文章主要介紹了使用element ui中el-table-column進行自定義校驗方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08

