要理解 computed 的工作原理,只需要理解下面4个特性
- 特性1:computed默认不执行(因为 lazy 的原因,在新建watcher实例的时候,会将 watcher.value 赋值为
undefined,而不会立马进行计算。)
- 特性2:取值的时候,computed里面的方法会被执行
-
特性3:computed是惰性的,computed依赖的其它属性发生变化时,computed不会立即重新计算,要等到获取computed的值,也就是求值的时候才会重新计算(依靠watcher中的lazy属性判断,如果lazy是true,则不执行函数)
-
特性4:computed是缓存的,如果computed依赖的其它属性没有发生变化,即使重新调用,也不会重新计算(依靠watcher中的dirty属性判断,如果dirty是true,则重新计算,否则不计算,直接取watcher内的缓存value)
用法
Vue中computed典型的用法有如下两种:
// 方式1: computed: { fullName() { return this.firstName + this.lastName } } //
方式2: computed: { fullName: { get() { console.log('ooo') return this.firstName +
this.lastName }, set() { console.log("set full name") } } }
computed源码实现
vue在初始化的时候,如果发现传入的属性是一个computed,则对其进行初始化处理
export function initState(vm: Component) { const opts = vm.$options if
(opts.props) initProps(vm, opts.props) // Composition API initSetup(vm) if
(opts.methods) initMethods(vm, opts.methods) if (opts.data) { initData(vm) }
else { const ob = observe((vm._data = {})) ob && ob.vmCount++ } if
(opts.computed) initComputed(vm, opts.computed) // 初始化 if (opts.watch &&
opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } }
可以看出初始化的优先级是 props->setup->methods->data->computed->watch
initComputed函数
function initComputed(vm: Component, computed: Object) { const watchers =
(vm._computedWatchers = Object.create(null)) for (const key in computed) {
const userDef = computed[key] // 对 computed 对象遍历,如果定义的是函数则直接取函数,不是则取对象内的get方法
const getter = isFunction(userDef) ? userDef : userDef.get if (__DEV__ &&
getter == null) { warn(`Getter is missing for computed property "${key}".`, vm)
} // 定义一个computed-wacther watchers[key] = new Watcher( vm, getter || noop,
noop, { lazy: true } ) // 将key定义在vm上 defineComputed(vm, key, userDef) } }
*
computed是一个对象,首先要对其进行遍历。基于其用法的两种不同形式(函数和对象),会对其进行判断。对 computed
对象遍历,如果定义的是函数则直接取函数,不是则取对象内的get方法。
*
在这里需要将每个computed的属性生成的watcher维护一个watchers,并且放在vm实例上。这样做的用处是,在创建computed getter
的时候,可以顺利的获取到它的dirty属性。而dirty=lazy=true。
*
然后将计算后的属性,定义到vm上
defineComputed方法
function defineComputed(vm, key, userDef) { const shouldCache =
!isServerRendering() if (isFunction(userDef)) {
//判断是否为缓存,如果不是每一次取值都会走get,如果发现是脏的,就重新获取,如果不是脏的,就不走get
sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) :
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 }
Object.defineProperty(target, key, sharedPropertyDefinition) } function
createGetterInvoker(fn) { return function computedGetter() { return
fn.call(this, this) } }
在这个方法中,维护了一个sharedPropertyDefinition对象,用来存储defineProperty的第三个参数。这个对象中的get
方法是一个自定义的get方法。即createComputedGetter(key)
之所以要用自定义的,是因为computed取值的时候,是有缓存的,如果没有变化,则不计算
从这里可以看出,computed依赖值发生变化的时候,是调用createComputedGetter的方法的。
createComputedGetter
一旦computed依赖的值发生变化,就会立刻进入这个方法。
function createComputedGetter(key) { return function computedGetter() { const
watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) {
// 重点这段 if (watcher.dirty) { watcher.evaluate() } if (Dep.target) { if (__DEV__
&& Dep.target.onTrack) { Dep.target.onTrack({ effect: Dep.target, target: this,
type: TrackOpTypes.GET, key }) } watcher.depend() } return watcher.value } } }
在这个方法中,根据每一个watcher实例的dirty属性来判断是否执行计算方法。并返回计算过后的值。因为初始化的时候 dirty = true
,初始化调用 watcher 的evaluate 方法,其实这里可以看出 computed 的缓存就是通过dirty
属性来判断,缓存数据存储在watcher的value属性。
watcher.get(重点:依赖数据watcher和computed的watcher互相绑定)
evaluate() { this.value = this.get() this.dirty = false } get() {
pushTarget(this) // Dep.target = target 通过Dep保存当前 computed-watcher(Dep.target =
target)并调用我们传入的函数 let value const vm = this.vm try { value =
this.getter.call(vm, vm) } catch (e: any) { 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 } export function pushTarget(target?: DepTarget | null) {
targetStack.push(target) Dep.target = target }
Dep.target =
target,通过Dep保存当前 computed-watcher(Dep.target = target),所以当前的全局Dep.target保存的是
computed-watcher。当调用 this.getter.call(vm, vm) 会触发依赖项内部的属性的get方法。例如我们使用的是
fullName() { return this.firstName + this.lastName },当计算属性调用fullName
函数,触发 this.firstName 和 this.lastName的 get 方法(Observer类内)。
Object.defineProperty(obj, key, { enumerable: true, configurable: true, get:
function reactiveGetter() { const value = getter ? getter.call(obj) : val if
(Dep.target) { dep.depend() } return isRef(value) && !shallow ? value.value :
value } }) depend(info?: DebuggerEventExtraInfo) { if (Dep.target) { // 调用
computed-watcher 内的 addDep 方法 Dep.target.addDep(this) } }
因为get方法是瞬时同步的,会拦截访问提前执行,执行dep.depend(),而 Dep.target = computed-watcher,会执行
watcher 的 addDep 方法。并且将 this(依赖数据的watcher)带到computed-watcher。
addDep(dep: Dep) { const id = dep.id if (!this.newDepIds.has(id)) {
this.newDepIds.add(id) this.newDeps.push(dep) if (!this.depIds.has(id)) {
dep.addSub(this) } } }
实参 dep 是依赖数据的watcher,这时将computed的watcher添加到 data的watcher内 ,这样就实现data依赖 收集到
依赖computed的watcher,从而 data 变化时,会同时通知 computed 和 computed的地方。
需要注意的是,在computed的依赖属性的Dep上,收集了两个watcher:
* computed的watcher
* 渲染watcher
这两个watcher都会被执行。