Appearance
initComputed
javascript
function initComputed(vm: Component, computed: Object) {
// 保存所有的 computed watcher
const watchers = (vm._computedWatchers = Object.create(null))
// 遍历 computed
for (const key in computed) {
// 获取用户定义的 value,即 userDef,可能是函数,也可能是对象
const userDef = computed[key]
// 如果是对象,getter 就是 userDef.get
const getter = isFunction(userDef) ? userDef : userDef.get
// 创建 computed watcher 并保存进 vm._computedWatchers 中
// create internal watcher for the computed property.
watchers[key] = new Watcher(
vm,
getter || noop, // expOrFn
noop, // cb
computedWatcherOptions // { lazy: true },用于标识 computed watcher
)
if (!(key in vm)) {
// 生成 computed watcher 后通过 defineComputed 并传入vm、key、userDef 将
// computed 代理到 vm 上,实现模板中使用 computed 会去 vm 上找,由于
// computed 要实现懒执行,Object.defineProperty 中的 get 不能马上就把
// 用户定义的函数执行,包装成高阶函数,返回的函数内部
// this._computedWatchers[key] 取出对应的 computed watcher,
// 判断一下如果 watcher.dirty 为真,说明不走缓存,需要执行用户定义的函数,
// 通过 watcher.evaluate 方法内部执行 this.value = this.get() 获取
// 初始值或最新值,即 this.getter.call(vm, vm),再 this.dirty = false 保证之
// 后走缓存,最后返回 watcher.value。
// 但依赖的值变化时需要 computed watcher 更新,不走缓存。因此我们需要让依赖
// 的值和 computed watcher 完成互相收集。在 evaluate 中我们执行了 this.get()
// 会执行用户定义的函数,而函数内部 this.xxx 使用到 data 中的数据就会触发
// Object.defineProperty 的 get,让依赖的值和 computed watcher 完成了互相
// 收集。
// 当依赖的值变化时,update 方法中判断一下 this.lazy 是否为真,为真说明是
// computed watcher,需要让我们定义的函数重新执行,因此 this.dirty = true,
// 当后续访问时,get 中发现 watcher.dirty 为真,会执行 watcher.evaluate
// 获取最新的值。此时我们需要重新执行 render 函数,去 vm 上重新访问 computed,
// 获取最新的值,也就是需要更新视图。但此时依赖的值改变时视图并不会更新。
// 先 initState, 再 $mount,所以先有 computed watcher, 才有 render watcher,
// 但 Dep.target 先是 render watcher, 才是computed watcher,
// 由于 computed watcher 初始化构造器中不执行 this.get,Dep.target 为空,
// render watcher 初始化默认执行 this.get, Dep.target = render watcher,
// this.get() 内部执行 render 函数时访问 computed, 触发高阶函数返回的那个函数,
// 因为是初始化, watcher.dirty 为真,从而触发 this.evaluate(),内部执行
// this.get() 获取初始值,pushTarget 会将 Dep.target 更新为 computed watcher,
// this.getter.call(vm, vm) 执行我们定义的函数去访问 computed 所依赖的值,
// 触发依赖的值的 get, 完成了依赖的值的 dep 和 computed watcher 的互相收集,
// 依赖的值变化时会让 computed watcher 更新, update 中 this.dirty = true。
// 我们应该让依赖的值的 dep 收集 render watcher, 实现依赖的值变化, 视图更新,
// 视图更新就会重新访问 computed,此时高阶函数返回的函数中通过
// this.dirty = true 从而执行 watcher.evaluate,内部执行 this.get 获取到新值,
// 再 this.dirty = false 之后走缓存。但是依赖的值的 dep 收集不到
// render watcher,因此 computed 获取初始值,也就是第一次执行 this.get() 时,
// pushTarget(this) 让 computed watcher 把 render watcher 覆盖了, 无法
// 收集到, 当依赖的值变化时,无法通知 render watcher 更新, 除非模板中也正好使用
// 了依赖的值。为了解决这个问题,使用栈存储 watcher,render watcher 会先入栈,
// computed watcher 会后面入栈, 依赖的值的 dep 和 computed watcher 互相收集完
// 后会 popTarget, 此时出栈并将 Dep.target 设置为栈顶, 此时就是
// render watcher,获取 computed 初始值时会执行 evaluate,执行完后判断
// Dep.target 是否有值, 如果有值就要让依赖的值的 dep 收集 render watcher,
// 依赖的值的 dep 存放在 computed wathcer 的 deps 中, 这就是为什么
// computed watcher 和 dep 要互相收集的原因。通过调用 watcher.depend,
// 内部让每一个依赖的值的 dep 调用 depend(), 完成依赖的值的 dep 和
// render watcher 的互相收集,实现依赖的值变化视图也会更新。
defineComputed(vm, key, userDef)
} else if (__DEV__) {
if (key in vm.$data) {
// key 已经在 data 中定义了
warn(`The computed property "${key}" is already defined in data.`, vm)
} else if (vm.$options.props && key in vm.$options.props) {
// key 已经在 props 中定义了
warn(`The computed property "${key}" is already defined as a prop.`, vm)
} else if (vm.$options.methods && key in vm.$options.methods) {
// key 已经在 methods 中定义了
warn(
`The computed property "${key}" is already defined as a method.`,
vm
)
}
}
}
}
class Watcher implements DepTarget {
vm?: Component | null
cb: Function
id: number
lazy: boolean
dirty: boolean
getter: Function
value: any
constructor(
vm: Component | null,
expOrFn: string | (() => any),
cb: Function,
options?: WatcherOptions | null
) {
// 处理 options
if (options) {
this.lazy = !!options.lazy
} else {
this.lazy = false
}
this.cb = cb
this.id = ++uid // uid for batching
// 用于控制 computed watcher 是懒惰的,走缓存。
// 值为 true 不走缓存,初始化的值和 lazy 值一样,都是 true。
this.dirty = this.lazy // for lazy watchers
if (isFunction(expOrFn)) {
// 对于 render watcher 来说 expOrFn 就是 updateComponnet,是函数。
// 对于 computed watcher 来说 expOrFn 就是 userDef 或 userDef.get
this.getter = expOrFn
} else {
// ...
}
// 实现用户 computed 初始化不执行,在真正使用时才执行。
this.value = this.lazy ? undefined : this.get()
}
}
function defineComputed(
target: any,
key: string,
userDef: Record<string, any> | (() => any)
) {
if (isFunction(userDef)) {
sharedPropertyDefinition.get = createComputedGetter(key)
sharedPropertyDefinition.set = noop
} else {
sharedPropertyDefinition.get = userDef.get || noop
sharedPropertyDefinition.set = userDef.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
}
}
}
depend() {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}