这道考察computed属性的题蛮有意思的。
不单单考察了computed,并且还考察了vue的依赖收集以及脏检查。vue
computed : { foo() { if(this.a>0){ return this.a} else { return this.b + this.c } } } data() { a: 1, b: 1, c: 1, }
源码分析git
基于源码分析拆解执行表现github
foo()的返回值为this.b+this.c,2。这是正常的。缓存
foo()的返回值仍旧为this.a,1。
按照正常逻辑:一个对返回结果不会发生影响的操做,不须要再去作一遍无用的计算。
所以vue并无收集this.b和this.c。函数
a的值初始化为-1时,vue会搜集全部属性到deps。
由于this.a调用了getter,this.b+this.c也调用了各自的getter。oop
vue对this.b = 2,foo()返回1的优化是如何的呢?
下面咱们来看源码:
源码地址:state.js
computed相关的有三个很是重要的函数:源码分析
const computedWatcherOptions = { lazy: true }
function initComputed(vm: Computed, computed: Object){ // 建立_comptedWatchers用来收集watcher const watchers = vm._computedWatchers = Object.create(null) const isSSR = isServerRendering() for (const key in computed) { const userDef = computed[key] // 对不一样形式的computed形式作解析 foo(){ return this.a}或foo(){getter(){ return this.a}} const getter = typeof userDef === 'function' ? userDef : userDef.get if (!isSSR) { // 为计算属性建立内部的watcher,将getter做为依赖传入watcher watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ) } // 如果计算属性已经在组件的prototype错误定义了,手动定义 if (!(key in vm)) { defineComputed(vm, key, userDef) } }
export function defineComputed ( target: any, key: string, userDef: Object | Function ) { // 不是服务端渲染的状况下,须要缓存 // 而且设置getter为createComputedGetter const shouldCache = !isServerRendering() // function方式的计算属性 if (typeof userDef === 'function') { sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) : createGetterInvoker(userDef) sharedPropertyDefinition.set = noop } else { // set方式的计算属性 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 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 } } }
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 } 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.newDepIds.has(id)) { this.newDepIds.add(id) this.newDeps.push(dep) if (!this.depIds.has(id)) { dep.addSub(this) } } } }
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
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; }
_computedWatchers:{ foo: Watcher(vm, getter, null, { lazy: true }) } // watcher Watcher: { lazy: true, dirty: true, value: undefined }
// 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 }
优化
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.newDepIds.has(id)) { this.newDepIds.add(id) this.newDeps.push(dep) if (!this.depIds.has(id)) { dep.addSub(this) } } } // dep.addSub()拆解 addSub (sub: Watcher) { this.subs.push(sub) }
最终结果为:
计算属性foo仅仅收集了this.a做为dep。没有收集b和c。
[{Dep A{ lazy: true, dirty: false, value: this.a }}]this
[Dep A(1)]lua
当咱们执行this.b = 2时,b的setter发出依赖更新,getter执行更新。
可是,因为咱们初始化的条件仅仅将this.a做为计算属性foo的依赖,因此不会有任何变化。
// Watcher { lazy: true, dirty: false, value: this.a } 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 } }
get()的pushTarget(this)
vue收集this.a的依赖,vue收集this.b和this.c的依赖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=0,因为this.a的依赖被收集到,所以能够直接触发更新。
一个computed属性中,每一个相似this.foo的调用,都会执行依赖收集。当依赖收集多余一次时,视为脏(dirty)计算属性,须要从新计算computed属性再取值。对于干净的计算属性,vue直接取值便可。