new Vue的时候:执行 initComputed (vm, computed)
function initComputed (vm, computed) {
...
for (var key in computed) {
var userDef = computed[key];
var getter = typeof userDef === 'function' ? userDef : userDef.get;
if (!isSSR) {
// create internal watcher for the computed property.
watchers[key] = new Watcher(
vm,
getter || noop, // 这个值就是用户定义的computed: {key: val} 的val
noop,
computedWatcherOptions
);
}
...
}
所以本质上computed是一个watcher
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
...
if (typeof expOrFn === 'function') {
// 这里的expOrFn就是用户定义的computed{key: val}中的val
this.getter = expOrFn
}
...
}
假设用户computed中定义为
用户的computed中: {
fullName () {
// 'aa' + 'bb'
return this.firstName + this.lastName
}
}
假设这个时候firstName变成了'a' , lastName变成了'abb'
这两个值的变化都会触发派发更新
会触发watcher.update,当前的watcher为computed
update () {
/* istanbul ignore else */
if (this.computed) {
// A computed property watcher has two modes: lazy and activated.
// It initializes as lazy by default, and only becomes activated when
// it is depended on by at least one subscriber, which is typically
// another computed property or a component's render function.
if (this.dep.subs.length === 0) {
// 当你并没有使用过这个computed的时候,其实就可以啥都不做
// In lazy mode, we don't want to perform computations until necessary,
// so we simply mark the watcher as dirty. The actual computation is
// performed just-in-time in this.evaluate() when the computed property
// is accessed.
this.dirty = true
} else {
// In activated mode, we want to proactively perform the computation
// but only notify our subscribers when the value has indeed changed.
// 有使用的case
this.getAndInvoke(() => {
this.dep.notify()
})
}
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
上一步分析到:会执行
this.getAndInvoke(() => {
this.dep.notify() // this为当前的wacther
})
getAndInvoke
getAndInvoke (cb: Function) {
const value = this.get()
if (
// 这里会做一个比较,相同就是会走缓存,不会派发更新,但是会每次求值
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
isObject(value) ||
this.deep
) {
// set new value
const oldValue = this.value
this.value = value
this.dirty = false
if (this.user) {
// 我们在new Vue({watch: {}})写的watch
try {
cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
// 这里执行上一步的 () => {this.dep.notify()}
cb.call(this.vm, value, oldValue)
// 这里会派发更新,像普通的data.fullName值一样,会重新渲染vue
}
}
}
wacther.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
}
结论:
computed初始化的时候做了一个get求值,执行了一次用户定义的表达式,这个computed watcher会订阅firstName和lastName的(执行过程中有其他的响应式数据也会订阅),firtName或者lastName变化会触发computed表达式执行,但是当新值和旧值相等时,不会触订阅了fullName的watcher更新。所以会有缓存一说。
网友评论