constcomputedWatcherOptions={lazy:true}functioninitComputed(vm:Component,computed:Object){ // $flow-disable-lineconstwatchers= (vm._computedWatchers=Object.create(null)) // computed properties are just getters during SSRconstisSSR=isServerRendering()for (constkeyincomputed) {constuserDef=computed[key]constgetter=typeofuserDef==='function'?userDef:userDef.getif (process.env.NODE_ENV!=='production'&&getter==null) {warn(`Getter is missing for computed property "${key}".`,vm)}if (!isSSR) { // create internal watcher for the computed property.watchers[key] =newWatcher(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 (!(keyinvm)) {defineComputed(vm,key,userDef)}elseif (process.env.NODE_ENV!=='production') {if (keyinvm.$data) {warn(`The computed property "${key}" is already defined in data.`,vm)}elseif (vm.$options.props&&keyinvm.$options.props) {warn(`The computed property "${key}" is already defined as a prop.`,vm)}}}}
对 deep watcher 的理解非常重要,今后工作中如果大家 watch 了一个复杂对象,并且希望改变对象内部深层某个值的时候也触发回调,一定要设置 deep 为 true,但是因为设置了 deep 后会执行 traverse 函数,会有一定的性能开销,所以一定要根据应用场景权衡是否要开启这个配置。
user watcher
前面我们分析过,通过 vm.$watch 创建的 watcher 是一个 user watcher,其实它的功能很简单,在对 watcher 求值以及在执行回调函数的时候,会处理一下错误,如下:
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/
evaluate () {
this.value = this.get()
this.dirty = false
}
/**
* Depend on all deps collected by this watcher.
*/
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
/** src/core/observer/watcher.js */
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
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)
}
}
}
/** src/core/observer/watcher.js */
get() {
//……
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
//……
}
/**
* Recursively traverse an object to evoke all converted
* getters, so that every nested property inside the object
* is collected as a "deep" dependency.
*/
const seenObjects = new Set()
function traverse(val: any) {
seenObjects.clear()
_traverse(val, seenObjects)
}
function _traverse(val: any, seen: ISet) {
let i, keys
const isA = Array.isArray(val)
if ((!isA && !isObject(val)) || !Object.isExtensible(val)) {
return
}
if (val.__ob__) {
const depId = val.__ob__.dep.id
if (seen.has(depId)) {
return
}
seen.add(depId)
}
if (isA) {
i = val.length
while (i--) _traverse(val[i], seen)
} else {
keys = Object.keys(val)
i = keys.length
while (i--) _traverse(val[keys[i]], seen)
}
}
/** src/core/observer/watcher.js 中 Watcher 构造函数的 get 实例方法中*/
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}