1. 程式人生 > >vue中的computed實現原理

vue中的computed實現原理

vue中的computed的實現原理---------需要建立資料依賴蒐集,動態計算實現原理

1)問題:計算屬性如何與屬性建立依賴關係?屬性發生變化又如何通知到計算屬性重新計算?

如何建立依賴關係?----------利用 JavaScript 單執行緒原理和 Vue 的 Getter 設計,通過一個簡單的釋出訂閱,就可以在一次計算屬性求值的過程中收集到相關依賴

2)data 屬性初始化 getter setter:通過Object.defineProperty()給data設定setter  getter

// src/observer/index.js
 
// 這裡開始轉換 data 的 getter setter,原始值已存入到 __ob__ 屬性中
Object.defineProperty(obj, key, {
 enumerable: true,
 configurable: true,
 get: function reactiveGetter () {
  const value = getter ? getter.call(obj) : val
  // 判斷是否處於依賴收集狀態
  if (Dep.target) {
   // 建立依賴關係
   dep.depend()
   ...
  }
  return value
 },
 set: function reactiveSetter (newVal) {
  ...
  // 依賴發生變化,通知到計算屬性重新計算
  dep.notify()
 }
})

3)computed屬性初始化

// src/core/instance/state.js
 
// 初始化計算屬性
function initComputed (vm: Component, computed: Object) {
 ...
 // 遍歷 computed 計算屬性
 for (const key in computed) {
  ...
  // 建立 Watcher 例項
  // create internal watcher for the computed property.
  watchers[key] = new Watcher(vm, getter || noop, noop, computedWatcherOptions)
 
  // 建立屬性 vm.reversedMessage,並將提供的函式將用作屬性 vm.reversedMessage 的 getter,
  // 最終 computed 與 data 會一起混合到 vm 下,所以當 computed 與 data 存在重名屬性時會丟擲警告
  defineComputed(vm, key, userDef)
  ...
 }
}
 
export function defineComputed (target: any, key: string, userDef: Object | Function) {
 ...
 // 建立 get set 方法
 sharedPropertyDefinition.get = createComputedGetter(key)
 sharedPropertyDefinition.set = noop
 ...
 // 建立屬性 vm.reversedMessage,並初始化 getter setter
 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 方法用於取值操作
    watcher.evaluate()
   }
   // 同第1步,判斷是否處於依賴收集狀態
   if (Dep.target) {
    watcher.depend()
   }
   return watcher.value
  }
 }
}

4)無論是屬性還是計算屬性,都會生成一個對應的 watcher 例項

// src/core/observer/watcher.js
 
// 當通過 vm.reversedMessage 獲取計算屬性時,就會進到這個 getter 方法
get () {
 // this 指的是 watcher 例項
 // 將當前 watcher 例項暫存到 Dep.target,這就表示開啟了依賴收集任務
 pushTarget(this)
 let value
 const vm = this.vm
 try {
  // 在執行 vm.reversedMessage 的函調函式時,會觸發屬性(步驟1)和計算屬性(步驟2)的 getter
  // 在這個執行過程中,就可以收集到 vm.reversedMessage 的依賴了
  value = this.getter.call(vm, vm)
 } catch (e) {
  if (this.user) {
   handleError(e, vm, `getter for watcher "${this.expression}"`)
  } else {
   throw e
  }
 } finally {
  if (this.deep) {
   traverse(value)
  }
  // 結束依賴收集任務
  popTarget()
  this.cleanupDeps()
 }
 return value
}

5)Dep 的程式碼短小精悍,但卻承擔著非常重要的依賴收集環節

// src/core/observer/dep.js
 
export default class Dep {
 static target: ?Watcher;
 id: number;
 subs: Array<Watcher>;
 
 constructor () {
  this.id = uid++
  this.subs = []
 }
 
 addSub (sub: Watcher) {
  this.subs.push(sub)
 }
 
 removeSub (sub: Watcher) {
  remove(this.subs, 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++) {
   // 更新 watcher 的值,與 watcher.evaluate() 類似,
   // 但 update 是給依賴變化時使用的,包含對 watch 的處理
   subs[i].update()
  }
 }
}
 
// 當首次計算 computed 屬性的值時,Dep 將會在計算期間對依賴進行收集
Dep.target = null
const targetStack = []
 
export function pushTarget (_target: Watcher) {
 // 在一次依賴收集期間,如果有其他依賴收集任務開始(比如:當前 computed 計算屬性巢狀其他 computed 計算屬性),
 // 那麼將會把當前 target 暫存到 targetStack,先進行其他 target 的依賴收集,
 if (Dep.target) targetStack.push(Dep.target)
 Dep.target = _target
}
 
export function popTarget () {
 // 當巢狀的依賴收集任務完成後,將 target 恢復為上一層的 Watcher,並繼續做依賴收集
 Dep.target = targetStack.pop()
}

總結一下依賴收集、動態計算的流程:

1. data 屬性初始化 getter setter

2. computed 計算屬性初始化,提供的函式將用作屬性 vm.reversedMessage 的 getter

3. 當首次獲取 reversedMessage 計算屬性的值時,Dep 開始依賴收集

4. 在執行 message getter 方法時,如果 Dep 處於依賴收集狀態,則判定 message 為 reversedMessage 的依賴,並建立依賴關係

5. 當 message 發生變化時,根據依賴關係,觸發 reverseMessage 的重新計算