這道考察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直接取值便可。