Vue源碼閱讀(二):數據響應式與實現

--本文采自本人公衆號【猴哥別瞎說】java

這篇文章咱們來詳細瞭解數據響應式的原理與具體實現。react

聊到 Vue 的數據響應,不少人都會對其非侵入式的數據響應系統津津樂道,大概都知道它是經過數據劫持的方式(修改 Object.defineProperty() )來輕量化實現數據響應式。express

所謂輕量化,指的是:Vue 中的數據模型僅僅是一個個普通的 javaScript 對象,當使用者修改它們的時候,對應的頁面 UI 會進行更新。無需直接操做 DOM 元素的同時,對於數據模型的狀態管理也變得簡單明瞭。數組

原理

回顧上篇文章,咱們講到了Watcher: 它與Vue組件實例之間是一一對應的關係。說點題外話:實際上啊,在Vue1.x系列的時候,每個響應式變量都會有一個Watcher。開發者發現這樣的粒度太細了,因而在Vue2.x的時候,就變成了更高粒度的劃分:一個Vue組件實例對應一個Watcher。bash

Vue的數據化響應的具體實現,實際上依賴的還有兩個重要成員:Observer 與 Dep。Observer、Dep、Watcher三者之間的關係在 Vue2.x 中可經過下圖簡單展現:函數

屏幕快照 2019-11-20 上午7.53.04.png-63.9kB

Observer、Dep、Watcher 之間,經過發佈-訂閱模式的方式來進行交互。在數據初始化的時候,Watcher 就會訂閱對應變量的 Dep。當有數據變化的時候,Observer 經過數據劫持的方式,將數據的變動告知 Dep,而 Dep 則會通知有關聯關係的 Watcher 進行數據更新。正如上一節課講到的,Watcher的notify 過程當中調用了 updateComponent,其包含了兩個重要步驟:render 與 update。這兩個步驟最終會更新真實頁面。post

在一個 Vue 組件實例中,Watcher 只有一個。而實例中的每個響應式變量都會有一個 Dep。因而,一個組件中的 Watcher 與 Dep 之間的關係,是一對多的關係。ui

而現實應用中,Vue 組件確定不止一個啊。組件內部還會嵌套組件,而響應式變量有可能會與多個組件產生關聯。因而,在這個層面上,Dep 會對應多個 Watcher。this

綜上,Watcher 與 Dep 之間,是多對多的關係。spa

源碼解析

咱們的目標是:嘗試經過閱讀源碼的方式,將整個知識點串起來。

沿着上節課的引子,咱們能夠在src/core/instance/init.js文件的initState()函數中查看:

function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}
複製代碼

initState 中有不少須要初始化的屬性:props/methods/coumputed。咱們此時只關注 data 部分。留意到observe()方法,進入src/core/observer/index.js(與數據響應式相關的代碼都是src/core/observer/內)可知:

function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}
複製代碼

Observer

能夠看到,observe()的做用就是返回一個 Observer 對象。因而重點來了:

export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data

  //值得留意:Observer對象在一個Vue組件實例中存在多個,取決於data數據嵌套了幾個Object對象或數組
  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    //若是是數組
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      this.observeArray(value)
    } else {
      //若是是對象
      this.walk(value)
    }
  }
複製代碼

只看是對象的狀況,因而進入到walk()方法:

walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }
複製代碼

這裏就能夠知道,Observer 的做用就是:針對 data 對象的每個屬性,分別對其進行數據響應化處理。值得留意:Observer 對象在一個 Vue 組件實例中存在多個,取決於 data 數據嵌套了幾個 Object 對象或數組。

Observer 是如何與 Dep、Watcher 關聯起來的?咱們先來看看 Dep、Watcher 長啥樣子,而後再來進入到最核心的defineReactive()

Dep

看看 Dep 的結構吧:

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 () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}
複製代碼

從內部變量屬性可知,其包含的靜態變量 target 是一個 Watcher,其包含的常規變量 subs 是 Watcher 數組。其內部主要的兩個方法:depend()關聯對應的 Watcher,notify()通知對應的 Watcher 進行 update 操做。

Watcher

Watcher 的結構以下:

export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  deep: boolean;
  user: boolean;
  lazy: boolean;
  sync: boolean;
  dirty: boolean;
  active: boolean;
  deps: Array<Dep>;
  newDeps: Array<Dep>;
  depIds: SimpleSet;
  newDepIds: SimpleSet;
  before: ?Function;
  getter: Function;
  value: any;

  get () {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    return value
  }

  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)
      }
    }
  }

  update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }
}

複製代碼

這裏對 Watcher 的代碼進行了必定的簡化。經過聲明可知:deps變量是 Dep 數組。其核心方法有這三個:addDeps()與 Dep 之間相互關聯,get()調用 updateComponent 方法,update()執行批量更新操做。

Dep 中的 subs 爲 Watcher 數組,Watcher 中的 deps 爲 Dep 數組。也驗證了以前的描述:

Watcher與Dep之間,是多對多的關係。
複製代碼

defineReactive

此刻,咱們進入到最核心的defineReactive()

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  //一個key 一個dep(),一一對應
  const dep = new Dep()

  ...//忽略

  //val若是是對象或者數組,會遞歸observe
  //每個對象或數組的出現,都會出現一個新的Observer
  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      //
      //值得注意的是,在initState的時候,並無觸發Dep.target,由於尚未Watcher生成,
      //Watcher的產生是在第一次$mounted的過程當中生成的
      //而之後每次觸發Dep.pushTarget的時候,都會將Dep.target再次被引用到具體的Watcher
      //好比:
      //   watcher.js中的 get()
      //     state.js中的 getData()
      // lifecycle.js中的 callHook()
      if (Dep.target) {
        //depend()是相互添加引用的過程
        //一個Vue實例只有一個Watcher,一個key就有一個Dep
        //在單一Vue組件實例中,Watcher與Dep之間,是一對多的關係
        //考慮到Vue實例存在嵌套(或用戶手寫了watch表達式),Dep中會保存多個Watcher(存在subs數組中)
        //這樣,當key發生變化時,對應的Watcher的notify()方法就會被觸發,對應Vue實例就會更新頁面
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      ...//忽略
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      //特別處理:若是最新賦值是對象,該對象仍然須要響應化處理
      childOb = !shallow && observe(newVal)
      //Dep通知更新
      dep.notify()
    }
  })
}
複製代碼

從代碼可知:

  1. 每個 data 變量都會有一個 Dep。
  2. get data 變量的時候,會觸發dep.depend(),將 Dep 與 Watcher 之間進行關聯。
  3. set data 變量的時候,會觸發dep.notify(),通知 Dep 對應的 Watcher 進行對應的更新操做。

關聯上節課講到的,Watcher 更新的過程會觸發 updateComponent,因而會從新執行$._render()函數與$._update()函數,生成虛擬 DOM,進而更新真實 DOM 操做。

因而,這個針對對象的數據響應化過程,就基本走通了。在下一文章中,咱們來看看針對數組的數據響應化過程是怎樣的,它與對象的響應化過程有何不一樣?

相關文章
相關標籤/搜索