computed : { foo() { if(this.a>0){ return this.a} else { return this.b + this.c } } } data() { a: 1, b: 1, c: 1, }
众所周知,首次a,b,c均为1时,foo()返回值为1。
以foo()返回值为1作为起始态,独立的执行下面以下3个操作,vue会如何计算foo呢?
- 如果此时this.a = 0,foo()如何计算?
- 如果此时this.b = 2,foo()如何计算?
- 如果a的初始值为-1,执行this.a = 1,foo()如何计算?
目录
- 执行表现
- 源码分析
- createComputedGetter
- 关键的watcher.js
- 关键的dep.js
- 基于源码分析拆解执行表现
- 初始化a,b,c均为1时,foo()如何计算?
- 如果此时this.a = 0,foo()如何计算?
- 如果此时this.b = 2,foo()如何计算?
- 如果a的初始值为-1,执行this.a = 1,foo()如何计算?
- 一句话总结
执行表现
- 如果此时this.a = 0,foo()如何计算?
- 如果此时this.b = 2,foo()如何计算?
- 如果a的初始值为-1,执行this.a = 1,foo()如何计算?
如果此时this.a = 0,foo()如何计算?
foo()的返回值会从this.a变为this.b+this.c,2。
vue会重新执行一遍evaluate,得到返回值this.b+this.c。
如果此时this.b = 2,foo()如何计算?
foo()的返回值仍旧为this.a,1。
vue会跳过evaluate的阶段,直接得到返回值this.a。
如果a的初始值为-1,执行this.a = 1,foo()如何计算?
foo()的返回值会从this.b+this.c变为this.a。
vue会重新执行一遍evaluate,得到返回值this.a。
为什么会是这样的呢?是否执行evaluate的条件是什么?
为什么a的初始值为-1了也可以重新evaluate?
源码分析
对于this.b = 2,vue跳过evaluate阶段,直接得到返回值this.a,是如何优化的呢?
下面我们来看源码:
源码地址:state.js
computed相关的有三个非常重要的函数:
- createComputedGetter(脏检查,依赖收集)
- 关键的watcher.js(Watcher)
- 关键的dep.js(依赖)
先来看看最最核心的代码
// 脏检查, 执行计算 if (watcher.dirty) { watcher.evaluate() } // Dep更新依赖 if (Dep.target) { watcher.depend() }
下面再看具体的源码
createComputedGetter
- 脏检查, 重新计算 对__computedWatcher中的具体的属性做检查
- Dep更新依赖
function createComputedGetter (key) { return function computedGetter () { const watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) { // 脏检查, 执行计算 if (watcher.dirty) { watcher.evaluate() } // Dep更新依赖 if (Dep.target) { watcher.depend() } return watcher.value } } }
关键的watcher.js
- get 收集依赖
- udpate 更新dirty为true,从而可以重新evaluate
- evaluate 重新获得computed属性的值
- depend 通知dep收集依赖
- addDep watcher订阅依赖
export default class Watcher { lazy: boolean; dirty: boolean; constructor ( ) { this.dirty = this.lazy // for lazy watchers,dirty用于懒监听 this.value = this.lazy? undefined: this.get() // Dep的target设置为foo watcher } // Evaluate the getter, and re-collect dependencies. get () { pushTarget(this) value = this.getter.call(vm, vm) return value; } update () { if (this.lazy) { this.dirty = true } } evaluate () { this.value = this.get() this.dirty = false } depend () { let i = this.deps.length while (i--) { this.deps[i].depend() } } addDep (dep: Dep) { const id = dep.id if (!this.depIds.has(id)) { dep.addSub(this) } } }
关键的dep.js
- addSub:添加订阅
- depend: 添加依赖
- notify:通知更新
export default class Dep { constructor () { this.subs = [] } addSub (sub: Watcher) { this.subs.push(sub) } depend () { if (Dep.target) { Dep.target.addDep(this) } } notify () { const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } } Dep.target = null
基于源码分析拆解执行表现
- 初始化a,b,c均为1时,foo()如何计算?
- 如果此时this.a = 0,foo()如何计算?
- 如果此时this.b = 2,foo()如何计算?
- 如果a的初始值为-1,执行this.a = 1,foo()如何计算?
computed : { foo() { if(this.a>0){ return this.a} else { return this.b + this.c } } } data() { a: 1, b: 1, c: 1, } created(){ this.b = 2; }
初始化a,b,c均为1时,foo()如何计算?
- 初始化watcher
- 创建getter得到value并将dirty置为false
- watcher帮助dep收集依赖,收集的是this.a
- 依赖收集图
初始化watcher
_computedWatchers:{ foo: Watcher {(vm, getter, null, { lazy: true })} } // watcher Watcher: { lazy: true, dirty: true, value: undefined, deps:[] }
创建getter得到value并将dirty置为false
// Watcher: { lazy: true, dirty: true, value: undefined } const watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) { // 脏检查, 执行计算 if (watcher.dirty) { watcher.evaluate() // 得到value,dirty置为false } // 返回this.a 1 return watcher.value } // watcher.evaluate() 拆解 evaluate () { // 从foo的getter get()得到value:this.a 1 this.value = this.get() // 将dirty变为false this.dirty = false }
执行完毕后,结果为Watcher { lazy: true, dirty: false, value: this.a, deps: [Dep a]}
watcher帮助dep收集依赖
if (watcher) { // Dep更新依赖 if (Dep.target) { watcher.depend() } } // watcher.depend() 拆解 depend () { let i = this.deps.length while (i--) { this.deps[i].depend() } } // dep.depend()拆解 depend () { if (Dep.target) { Dep.target.addDep(this) } } // watcher.addDep拆解 addDep (dep: Dep) { const id = dep.id if (!this.depIds.has(id)) { dep.addSub(this) } } // dep.addSub()拆解 addSub (sub: Watcher) { this.subs.push(sub) }
最终结果为:
计算属性foo仅仅收集了this.a作为dep。没有收集b和c。
Watcher { lazy: true, dirty: false, value: this.a , deps: [Dep a]}
依赖收集图(dirty为false)
deps: [Dep a(1)]
如果此时this.a = 0,foo()如何计算?
- 执行computedGetter触发watcher.evaluate()
- 在收集了a的基础上,再次收集到b和c的依赖
- 依赖收集图
当我们执行this.a = 0时,a的setter发出依赖更新,getter执行更新,dirty由false变为true。
由于dirty为true,所以执行evaluate,得到foo()的返回值this.b+this.c。
// 发出依赖更新 notify () { const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } // dirty由false变为true update () { if (this.lazy) { this.dirty = true } } return function computedGetter () { const watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) { // 脏检查, 执行计算 if (watcher.dirty) { // 执行evaluate watcher.evaluate() } // Dep更新依赖 if (Dep.target) { watcher.depend() } return watcher.value } } evaluate () { this.value = this.get() this.dirty = false } get () { pushTarget(this) value = this.getter.call(vm, vm) return value; } // 收集b和c的依赖 if (Dep.target) { watcher.depend() }
最终收集到的依赖为Watcher: { lazy: true, dirty: true, value: this.b+this.c , deps: [Dep a, Dep b, Dep c]}
依赖收集图(dirty为true)
deps: [Dep a(1), Dep b(2), Dep c(2)]
如果此时this.b = 2,foo()如何计算?
- 执行computedGetter不会触发watcher.evaluate()
- 依赖收集图
执行computedGetter不会触发watcher.evaluate()
为什么执行computedGetter不会触发watcher.evaluate()?
因为仅收集了this.a的依赖
当我们执行this.b = 2时,b的setter发出依赖更新,getter执行更新。
但是,由于我们初始化的条件仅仅将this.a作为计算属性foo的依赖,所以不会有任何变化。
// Watcher { lazy: true, dirty: false, value: this.a,deps: [Dep a(1) } return function computedGetter () { const watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) { // 此时watcher的dirty为false if (watcher.dirty) { watcher.evaluate() } // 返回this.a的值 1 return watcher.value } }
依赖收集图(dirty为false)
deps: [Dep a(1)]
如果a的初始值为-1,执行this.a = 1,foo()如何计算?
- 每次get都会被收集到依赖中,收集a,b,c依赖到deps
- 由于this.a的依赖被收集到,因此可以直接通过this.a = 0触发更新
- 依赖收集图
源码get()的注释:
Evaluate the getter, and re-collect dependencies.
computed : { foo() { // a的get()触发,收集到deps if(this.a>0){ return this.a} // b和c的get()触发,收集到deps else { return this.b + this.c } } } data() { a: -1, b: 1, c: 1, }
如何收集的?
get () { pushTarget(this) value = this.getter.call(vm, vm) }
此时再触发this.a=1,由于this.a的依赖被收集到,因此可以直接触发更新。
最终返回1。
依赖收集图(dirty为true)
deps: [Dep a(1), Dep a(2), Dep a(2)]
一句话总结
一个computed属性中,每个类似this.foo的调用,都会在get()中重新收集依赖。当依赖收集大于一次(不是一个)时,视为脏(dirty)计算属性,需要重新 evaluate再取值。对于干净的计算属性,不需重新执行evaluate,vue直接取值即可。