Vue源碼解讀之computed計算屬性

前言

通過珠峯課程的學習來理解computed計算屬性的原理

那首先呢,先回顧一下vue的響應式數據原理

Vue 在初始化數據時,會給 data 中的所有屬性使用 Object.defineProperty 重新定義 setter 和 getter , 當頁面獲取到對應屬性時,會觸發 get 方法並進行依賴收集(收集當前組件的watcher) 如果屬性發生變化會通知相關依賴進行更新操作 。

其實呢,它們本質上沒有什麼區別,在前章分享響應式數據原理的時候我們就略帶過 computed 計算屬性,好,接着往下看:

響應式數據我們知道有個初始化 data 的方法叫做 initData , 那麼計算屬性當然也有自己的初始化叫做initComputed , 我們走進源碼:src/core/instance/state.js 169 行

function initComputed (vm: Component, computed: Object) { // 初始化計算屬性
  const watchers = vm._computedWatchers = Object.create(null) // 初始化 計算屬性 watcher 列表
  const isSSR = isServerRendering() // 判斷是否是服務器渲染

  for (const key in computed) {
    const userDef = computed[key] //注意:獲取用戶定義的方法
    // 就是計算屬性 computed 裏邊定義的自變量函數   ↑
    const getter = typeof userDef === 'function' ? userDef : userDef.get // 獲取
    if (process.env.NODE_ENV !== 'production' && getter == null) { // 環境判斷
      warn(
        `Getter is missing for computed property "${key}".`,
        vm
      )
    }
    if (!isSSR) { // 非服務器渲染
      watchers[key] = new Watcher( // 爲每一個計算屬性創建一個 watcher
        vm,
        getter || noop, // 將用戶定義的方法傳入
        noop,
        computedWatcherOptions // 注意:爲計算屬性options傳入 { lazy:true }
      )
    }
    if (!(key in vm)) {
      defineComputed(vm, key, userDef)  // 重點注意: 將計算屬性定義到實例上 , 方法實體在下邊
    } else if (process.env.NODE_ENV !== 'production') {
      if (key in vm.$data) {
        warn(`The computed property "${key}" is already defined in data.`, vm)
      } else if (vm.$options.props && key in vm.$options.props) {
        warn(`The computed property "${key}" is already defined as a prop.`, vm)
      }
    }
  }
}

export function defineComputed ( // vm  初始化的實例 ↑ 
  target: any,   
  key: string,
  userDef: Object | Function
) {
  const shouldCache = !isServerRendering()
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)  // 創建計算屬性的 getter 方法實體 ↓ 
      : createGetterInvoker(userDef) // 調用計算屬性的 getter 方法實體 ↓
    sharedPropertyDefinition.set = noop
  } else {
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key) // 創建計算屬性的 getter 方法實體 ↓
        : createGetterInvoker(userDef.get) // 調用計算屬性的 getter 方法實體 ↓
      : noop
    sharedPropertyDefinition.set = userDef.set || noop
  }
  if (process.env.NODE_ENV !== 'production' &&
    sharedPropertyDefinition.set === noop) {
    sharedPropertyDefinition.set = function () {  // 環境判斷
      warn(
        `Computed property "${key}" was assigned to but it has no setter.`,
        this
      )
    }
  }
  Object.defineProperty(target, key, sharedPropertyDefinition) // 響應式數據驅動更新 
}

 // 創建計算屬性 getter 方法
function createComputedGetter (key) {
  return function computedGetter () { // 取值的時候回調用此方法
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      if (watcher.dirty) { // 做了一個dirty 實現了緩存的機制 , 計算屬性 調用對應的 evaluate () 
        watcher.evaluate() // 如是計算屬性,則調用 evaluate() ;
      }
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }
}
// 調用計算屬性的 getter 方法
function createGetterInvoker (fn) { 
  return function computedGetter () {
    return fn.call(this, this)
  }
}

總結

通過上邊的代碼我們可以瞭解到:initComputed 主要是爲每一個計算屬性創建一個watcher並且在 options 裏邊傳入 lazy:true 計算屬性標識 , 然後再通過defineComputed 將計算屬性定義在實例上去進行一個響應式數據觀測 。

回顧
再次來到 watcher.jssrc/observer/watcher.js 166行

update () { // 更新數據方法 
    /* istanbul ignore else */
    if (this.lazy) { // 計算屬性  依賴的數據發生變化了 會讓計算屬性的watcher的dirty變成true
      this.dirty = true
    } else if (this.sync) { // 同步watcher
      this.run()
    } else {
      queueWatcher(this) // 將watcher放入隊列
    }
  }

src/observer/watcher.js 212行 重點 , 重點 , 重點

evaluate () { // 計算屬性時,被調用自身的 get 方法
    this.value = this.get()
    this.dirty = false
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章