vue中created、watch和computed的执行顺序详解
作者:qb
前言
面试题:
vue
中created
、watch(immediate: true)
和computed
的执行顺序是啥?
先看个简单的例子:
// 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'); }, });
当前例子的执行顺序为:watch
--> created
--> computed
。
为什么?
在new Vue
的实例化过程中,会执行初始化方法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); } }
猛一看代码,是不是发现先执行的initComputed(vm, opts.computed)
,然后执行initWatch(vm, opts.watch)
,再执行callHook(vm, 'created')
,那为啥不是computed
--> watch
--> created
呢?
不要着急,听我娓娓道来。
1、关于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 }
作为参数传入,并且实例化的Watcher
中的getter
就是当前例子中的computedCount
函数;其次,通过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)
的方式,将函数computedGetter
作为get
函数,只有当对key
进行访问的时候,才会触发其内部的逻辑。内部逻辑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
的时候才会触发computed
的计算,也就是在进行vm.$mount(vm.$options.el)
阶段才会执行到console.log('computed')
。
2、关于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
为数组,则遍历执行createWatcher
,否则直接执行createWatcher
。如果handler
是对象或者字符串时,将其进行处理,最终作为参数传入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
设置user
为true
,再将其作为参数传入去进行Watcher
的实例化。
当前例子中options.immediate
为true
,所以会执行cb.call(vm, watcher.value)
,也就是以vm
为主体,立刻执行cb
。当前例子中cb
就是handler
:
handler() { console.log('watch'); },
这里就解释了当前例子中console.log('watch')
是最先执行的。
然后,执行完initComputed
和initWatch
以后,就会通过callHook(vm, 'created')
执行到生命周期中的console.log('created')
了。
最后通过vm.$mount(vm.$options.el)
进行页面渲染的时候,会先去创建vNode
,这时就需要获取到computedCount
的值,进而触发其get
函数的后续逻辑,最终执行到console.log('computed')
。
附:为什么vue中的watch在mounted之后执行
首先,在调用mounted的时候,会进入到defineReactive函数,然后调用函数里面的set方法,将mounted中赋的新的值给传递过去,并通过调用dep.notify( )把消息发送给订阅者,继而更新订阅者的列表
后面才开始渲染watch进入Watcher的class
总结
关于
vue
中created
和watch
的执行顺序相对比较简单,而其中computed
是通过Object.defineProperty
为当前vm
进行定义,再到后续创建vNode
阶段才去触发执行其get
函数,最终执行到计算属性computed
对应的逻辑。
到此这篇关于vue中created、watch和computed执行顺序详解的文章就介绍到这了,更多相关vue created、watch和computed执行顺序内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!