默認computed也是一個watcher,具備緩存,只有當依賴的屬性發生變化才會更新視圖。
原理圖:

流程:computed watcher在defineReactive的get中訂閱屬性的變化(4),在defineReactive的set時觸發notify(4),notify調用每個訂閱了改屬性變化的watcher的update(3),update中將computed watcher 的dirty賦值為true(2),notify中其中一個watcher為渲染watcher,此時執行渲染,渲染時因為模板中使用了computed watcher對應的計算屬性,故而觸發計算屬性的get方法createComputedGetter,在createComputedGetter中調用computed watcher 的evaluate方法(1),evaluate方法調用computed watcher的this.get(5),進而調用用戶在計算屬性上定義的方法進行計算。
源碼:
1、定義computed 的getter方法(在dirty為true時,使用緩存數據)(src\core\instance\state.js):
const computedWatcherOptions = { lazy: true }
function initComputed (vm: Component, computed: Object) {
...
if (!isSSR) {
// create internal watcher for the computed property.
watchers[key] = new Watcher(
vm,
getter || noop,//將用戶定義傳到watcher中
noop,
computedWatcherOptions//lazy:true懶watcher
)
}
// 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') {
if (key in vm.$data) {
warn(`The computed property "${key}" is already defined in data.`, vm)
} else if (vm.$options.props && key in vm.$options.props) {
warn(`The computed property "${key}" is already defined as a prop.`, vm)
}
}
}
}
export function defineComputed (
target: any,
key: string,
userDef: Object | Function
) {
const shouldCache = !isServerRendering()
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)//創建計算屬性的getter,不是用用戶傳的
: createGetterInvoker(userDef)
sharedPropertyDefinition.set = noop
} else {
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: createGetterInvoker(userDef.get)
: noop
sharedPropertyDefinition.set = userDef.set || noop
}
if (process.env.NODE_ENV !== 'production' &&
sharedPropertyDefinition.set === noop) {
sharedPropertyDefinition.set = function () {
warn(
`Computed property "${key}" was assigned to but it has no setter.`,
this
)
}
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
function createComputedGetter (key) {
return function computedGetter () {//用戶取值的時候會調用此方法
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
if (watcher.dirty) {//dirty為true會去進行求值,這兒的dirty起到了緩存的作用
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
2、computed的getter方法中dirty的賦值時機(src\core\observer\watcher.js):
update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this) } }
3、update的調用時機(調用當前屬性的訂閱中每一個watcher的update)(src\core\observer\dep.js):
notify () { ... for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } }
4、notify的調用時機(src\core\observer\index.js中 defineReactive的set)以及watcher在defineReactive的get訂閱屬性的變化(其中dep.target的賦值是在new Watcher時的this.get):
export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { ... Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value }, set: function reactiveSetter (newVal) { ... dep.notify() } }) }
dep.target的賦值是在new Watcher時調用this.get(src\core\observer\watcher.js):
constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean ) { ... this.value = this.lazy ? undefined : this.get() }
get () { pushTarget(this) let value const vm = this.vm try { value = this.getter.call(vm, vm) } catch (e) { if (this.user) { handleError(e, vm, `getter for watcher "${this.expression}"`) } else { throw e } } finally { // "touch" every property so they are all tracked as // dependencies for deep watching if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() } return value }
5、使用用戶定義的計算屬性的getter方法計算值(src\core\observer\watcher.js):
get () { pushTarget(this) let value const vm = this.vm try { value = this.getter.call(vm, vm) } catch (e) { if (this.user) { handleError(e, vm, `getter for watcher "${this.expression}"`) } else { throw e } } finally { // "touch" every property so they are all tracked as // dependencies for deep watching if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() } return value }
