Vue原理解析之observer模塊

本文是針對[email protected]進行分析

observer是Vue核心中最重要的一個模塊(個人認爲),能夠實現視圖與數據的響應式更新,底層全憑observer的支持。

observer模塊在Vue項目中的代碼位置是src/core/observer,模塊共分爲這幾個部分:

  • Observer: 數據的觀察者,讓數據對象的讀寫操作都處於自己的監管之下

  • Watcher: 數據的訂閱者,數據的變化會通知到Watcher,然後由Watcher進行相應的操作,例如更新視圖

  • DepObserverWatcher的紐帶,當數據變化時,會被Observer觀察到,然後由Dep通知到Watcher

示意圖如下:

clipboard.png

Observer

Observer類定義在src/core/observer/index.js中,先來看一下Observer的構造函數

constructor (value: any) {
  this.value = value
  this.dep = new Dep()
  this.vmCount = 0
  def(value, '__ob__', this)
  if (Array.isArray(value)) {
      const augment = hasProto
      ? protoAugment
      : copyAugment
    augment(value, arrayMethods, arrayKeys)
    this.observeArray(value)
  } else {
    this.walk(value)
  }
}

value是需要被觀察的數據對象,在構造函數中,會給value增加__ob__屬性,作爲數據已經被Observer觀察的標誌。如果value是數組,就使用observeArray遍歷value,對value中每一個元素調用observe分別進行觀察。如果value是對象,則使用walk遍歷value上每個key,對每個key調用defineReactive來獲得該key的set/get控制權。

解釋下上面用到的幾個函數的功能:

  • observeArray: 遍歷數組,對數組的每個元素調用observe

  • observe: 檢查對象上是否有__ob__屬性,如果存在,則表明該對象已經處於Observer的觀察中,如果不存在,則new Observer來觀察對象(其實還有一些判斷邏輯,爲了便於理解就不贅述了)

  • walk: 遍歷對象的每個key,對對象上每個key的數據調用defineReactive

  • defineReactive: 通過Object.defineProperty設置對象的key屬性,使得能夠捕獲到該屬性值的set/get動作。一般是由Watcher的實例對象進行get操作,此時Watcher的實例對象將被自動添加到Dep實例的依賴數組中,在外部操作觸發了set時,將通過Dep實例的notify來通知所有依賴的watcher進行更新。

如果不太理解上面的文字描述可以看一下圖:

clipboard.png

Dep

DepObserverWatcher之間的紐帶,也可以認爲Dep是服務於Observer的訂閱系統。Watcher訂閱某個ObserverDep,當Observer觀察的數據發生變化時,通過Dep通知各個已經訂閱的Watcher

Dep提供了幾個接口:

  • addSub: 接收的參數爲Watcher實例,並把Watcher實例存入記錄依賴的數組中

  • removeSub: 與addSub對應,作用是將Watcher實例從記錄依賴的數組中移除

  • dependDep.target上存放這當前需要操作的Watcher實例,調用depend會調用該Watcher實例的addDep方法,addDep的功能可以看下面對Watcher的介紹

  • notify: 通知依賴數組中所有的watcher進行更新操作

Watcher

Watcher是用來訂閱數據的變化的並執行相應操作(例如更新視圖)的。Watcher的構造器函數定義如下:

constructor (vm, expOrFn, cb, options) {
  this.vm = vm
  vm._watchers.push(this)
  // options
  if (options) {
    this.deep = !!options.deep
    this.user = !!options.user
    this.lazy = !!options.lazy
    this.sync = !!options.sync
  } else {
    this.deep = this.user = this.lazy = this.sync = false
  }
  this.cb = cb
  this.id = ++uid // uid for batching
  this.active = true
  this.dirty = this.lazy // for lazy watchers
  this.deps = []
  this.newDeps = []
  this.depIds = new Set()
  this.newDepIds = new Set()
  this.expression = process.env.NODE_ENV !== 'production'
    ? expOrFn.toString()
    : ''
  if (typeof expOrFn === 'function') {
    this.getter = expOrFn
  } else {
    this.getter = parsePath(expOrFn)
    if (!this.getter) {
      this.getter = function () {}
      process.env.NODE_ENV !== 'production' && warn(
        `Failed watching path: "${expOrFn}" ` +
        'Watcher only accepts simple dot-delimited paths. ' +
        'For full control, use a function instead.',
        vm
      )
    }
  }
  this.value = this.lazy
    ? undefined
    : this.get()
}

參數中,vm表示組件實例,expOrFn表示要訂閱的數據字段(字符串表示,例如a.b.c)或是一個要執行的函數,cb表示watcher運行後的回調函數,options是選項對象,包含deepuserlazy等配置。

watcher實例上有這些方法:

  • get: 將Dep.target設置爲當前watcher實例,在內部調用this.getter,如果此時某個被Observer觀察的數據對象被取值了,那麼當前watcher實例將會自動訂閱數據對象的Dep實例

  • addDep: 接收參數dep(Dep實例),讓當前watcher訂閱dep

  • cleanupDeps: 清除newDepIdsnewDep上記錄的對dep的訂閱信息

  • update: 立刻運行watcher或者將watcher加入隊列中等待統一flush

  • run: 運行watcher,調用this.get()求值,然後觸發回調

  • evaluate: 調用this.get()求值

  • depend: 遍歷this.deps,讓當前watcher實例訂閱所有dep

  • teardown: 去除當前watcher實例所有的訂閱

Array methods

src/core/observer/array.js中,Vue框架對數組的pushpopshiftunshiftsortsplicereverse方法進行了改造,在調用數組的這些方法時,自動觸發dep.notify(),解決了調用這些函數改變數組後無法觸發更新的問題。在Vue的官方文檔中對這個也有說明:http://cn.vuejs.org/v2/guide/list.html#變異方法

本文轉自https://segmentfault.com/a/1190000008377887

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章