如何理解vue的computed?

computed.png
這道考察computed屬性的題蠻有意思的。
不單單考察了computed,並且還考察了vue的依賴收集以及髒檢查。vue

  • 若是此時this.a = 0,foo()如何計算?
  • 若是此時this.b = 2,foo()如何計算?
  • 若是a的初始值爲-1,執行this.a = 0,foo()如何計算?
computed : {
    foo() {
        if(this.a>0){ return this.a}
        else { return this.b + this.c }
    }
}
data() {
    a: 1,
    b: 1,
    c: 1,
}
  • 執行表現
  • 源碼分析git

    • 一行很重要的代碼
    • initComputed
    • defineComputed
    • createComputedGetter
    • 關鍵的watcher.js
    • 關鍵的dep.js
  • 基於源碼分析拆解執行表現github

    • 若是此時this.a = 0,foo()如何計算?
    • 若是此時this.b = 2,foo()如何計算?
    • 若是a的初始值爲-1,執行this.a = 0,foo()如何計算?
  • 一句話總結

執行表現

  • 若是此時this.a = 0,foo()如何計算?
  • 若是此時this.b = 2,foo()如何計算?

若是此時this.a = 0,foo()如何計算?

foo()的返回值爲this.b+this.c,2。這是正常的。緩存

若是此時this.b = 2,foo()如何計算?

foo()的返回值仍舊爲this.a,1。
按照正常邏輯:一個對返回結果不會發生影響的操做,不須要再去作一遍無用的計算。
所以vue並無收集this.b和this.c。函數

若是a的初始值爲-1,執行this.a = 0,foo()如何計算?

a的值初始化爲-1時,vue會蒐集全部屬性到deps。
由於this.a調用了getter,this.b+this.c也調用了各自的getter。oop

源碼分析

vue對this.b = 2,foo()返回1的優化是如何的呢?
下面咱們來看源碼:
源碼地址:state.js
computed相關的有三個很是重要的函數:源碼分析

  • 一行很重要的代碼
  • initComputed
  • defineComputed
  • createComputedGetter

一行很重要的代碼

const computedWatcherOptions = { lazy: true }

初始化計算屬性initComputed

  • 解析不一樣格式的computed函數
  • 爲計算屬性建立watchers:__computedWatchers
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)
    }
}

手動定義計算屬性 defineComputed

  • 非ssr端緩存函數
  • 非ssr端建立computed getter
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)
}

建立計算屬性的getter 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

  • udpate函數
  • evaluate函數
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)
      }
    }
  }
}

關鍵的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

基於源碼分析拆解執行表現

  • 若是此時this.a = 0,foo()如何計算?
  • 若是此時this.b = 2,foo()如何計算?
  • 若是a的初始值爲-1,執行this.a = 0,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; }

若是此時this.a = 0,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 }
建立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 }優化

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.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

依賴收集圖(dirty爲false)

[Dep A(1)]
imagelua

若是此時this.b = 2,foo()如何計算?

  • 僅收集了this.a的依賴
  • 執行computedGetter不會觸發watcher.evaluate()
  • 依賴收集圖
僅收集了this.a的依賴

當咱們執行this.b = 2時,b的setter發出依賴更新,getter執行更新。
可是,因爲咱們初始化的條件僅僅將this.a做爲計算屬性foo的依賴,因此不會有任何變化。

執行computedGetter不會觸發watcher.evaluate()
// 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
    }
}
依賴收集圖(dirty爲false)

image

若是a的初始值爲-1,執行this.a = 0,foo()如何計算?

  • get()的pushTarget(this) vue收集this.a的依賴,vue收集this.b和this.c的依賴
  • 因爲this.a的依賴被收集到,所以能夠直接經過this.a = 0觸發更新
  • 依賴收集圖
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的依賴被收集到,所以能夠直接觸發更新。

依賴收集圖(dirty爲true)

image

一句話總結

一個computed屬性中,每一個相似this.foo的調用,都會執行依賴收集。當依賴收集多餘一次時,視爲髒(dirty)計算屬性,須要從新計算computed屬性再取值。對於乾淨的計算屬性,vue直接取值便可。

相關文章
相關標籤/搜索