Vue原理解析之observer模塊

AugHagenaue 8年前發布 | 11K 次閱讀 Vue.js Vue.js開發

本文是針對 Vue@2.1.8 進行分析

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

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

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

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

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

示意圖如下:

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 進行更新。

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

Dep

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

Dep 提供了幾個接口:

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

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

  • depend : Dep.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 是選項對象,包含 deep 、 user 、 lazy 等配置。

watcher 實例上有這些方法:

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

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

  • cleanupDeps : 清除 newDepIds 和 newDep 上記錄的對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框架對數組的 push 、 pop 、 shift 、 unshift 、 sort 、 splice 、 reverse 方法進行了改造,在調用數組的這些方法時,自動觸發 dep.notify() ,解決了調用這些函數改變數組后無法觸發更新的問題。在Vue的官方文檔中對這個也有說明: http://cn.vuejs.org/v2/guide/list.html#變異方法

 

來自:https://segmentfault.com/a/1190000008377887

 

 本文由用戶 AugHagenaue 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!