Vue 2.5 數(shù)據(jù)綁定實現(xiàn)邏輯(一)整體邏輯

在概述中說到,Vue2.5的數(shù)據(jù)綁定主要是通過對象屬性的 getter 和 setter 的鉤子來實現(xiàn)的,總的來說是在初始化的時候建立起鉤子,并且在數(shù)據(jù)更新的時候通知相應(yīng)的 Watcher 去運行回調(diào)函數(shù)執(zhí)行更新等操作。

具體來講,要分以下幾步:

  1. 初始化實例對象時運行initState, 建立好props, data 的鉤子以及其對象成員的Observer, 對于computed 屬性,則建立起所有對應(yīng)的 Watcher 并且通過 Object.defineProperty 在vm對象上設(shè)置一個該屬性的 getter。同時還根據(jù)自定義的 watch 來建立相應(yīng)的 Watcher

  2. 執(zhí)行掛載操作,在掛載時建立一個直接對應(yīng)render(渲染)的 Watcher,并且編譯模板生成 render 函數(shù),執(zhí)行vm._update 來更新 DOM 。

  3. 此后每當(dāng)有數(shù)據(jù)改變,都將通知相應(yīng)的 Watcher 執(zhí)行回調(diào)函數(shù)。

而想要真正理解這里的邏輯, 則需要先搞清楚 Dep, Observer 和 Watcher 這三種對象的作用,以及定義的鉤子函數(shù)中究竟做了什么。

Dep (dependency // 依賴)

位置: src/core/observer/dep.js

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

Dep 就是一個 Watcher 所對應(yīng)的數(shù)據(jù)依賴,在這個對象中也存有一個 subs 數(shù)組,用來保存和這個依賴有關(guān)的 Watcher。其成員函數(shù)最主要的是 depend 和 notify ,前者用來設(shè)置某個 Watcher 的依賴,后者則用來通知與這個依賴相關(guān)的 Watcher 來運行其回調(diào)函數(shù)。

另外還可以看到的是,這個類還有一個靜態(tài)成員 target, 同時還有一個全局的棧,其中儲存的是正在運行的Watcher。

Dep.target = null
const targetStack = []

export function pushTarget (_target: Watcher) {
  if (Dep.target) targetStack.push(Dep.target)
  Dep.target = _target
}

export function popTarget () {
  Dep.target = targetStack.pop()
}

這里的壓棧和出棧的方法就不用多說了。

Observer

位置: src/core/observer/index.js

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

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

  /**
   * Walk through each property and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i], obj[keys[i]])
    }
  }

  /**
   * Observe a list of Array items.
   */
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

Observer 主要是用來監(jiān)視一個對象的變化,比如在 data 中存在一個對象成員,直接給該對象成員添加屬性并不會觸發(fā)任何鉤子函數(shù),但是這個對象又是數(shù)據(jù)的一部分,也就是說該對象發(fā)生變化也會導(dǎo)致DOM發(fā)生改變,因此要用 Observer 來監(jiān)視一個對象的變化并且在變化時通知與其相關(guān)的 Watcher 來運行回調(diào)函數(shù)。

可以看到,Observer 中存在一個 Dep,也就是一個依賴。

在建立 Observer 的時候會用到 observe 函數(shù)

export 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) {// 判斷該對象是否已經(jīng)存在 Observer
    ob = value.__ob__
  } else if (
    observerState.shouldConvert &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

這個函數(shù)主要是用來動態(tài)返回一個 Observer,首先判斷value如果不是對象則返回,然后檢測該對象是否已經(jīng)有 Observer,有則直接返回,否則新建并將 Observer 保存在該對象的 ob 屬性中(在構(gòu)造函數(shù)中進行)。

在建立 Observer 時,如果OB的是數(shù)組則對數(shù)組中每個成員執(zhí)行 observe 函數(shù),否則對每個對象屬性執(zhí)行 defineReactive 函數(shù)來設(shè)置 get set 鉤子函數(shù)。

Watcher

位置: src/core/observer/watcher.js

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;
  getter: Function;
  value: any;

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    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()
      : ''
    // parse expression for getter
    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()
  }

  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  get () {
    ......
  }

  /**
   * Add a dependency to this directive.
   */
  addDep (dep: Dep) {
    ......
  }

  /**
   * Clean up for dependency collection.
   */
  cleanupDeps () {
    ......
  }

  /**
   * Subscriber interface.
   * Will be called when a dependency changes.
   */
  update () {
    ......
  }

  /**
   * Scheduler job interface.
   * Will be called by the scheduler.
   */
  run () {
    ......
  }

  /**
   * Evaluate the value of the watcher.
   * This only gets called for lazy watchers.
   */
  evaluate () {
    ......
  }

  /**
   * Depend on all deps collected by this watcher.
   */
  depend () {
    ......
  }

  /**
   * Remove self from all dependencies' subscriber list.
   */
  teardown () {
    ......
  }
}

Watcher 在構(gòu)造時傳入的參數(shù)最重要的是 expOrFn , 這是一個 getter 函數(shù),或者可以用來生成一個 getter 函數(shù)的字符串,而這個 getter 函數(shù)就是之前所說的回調(diào)函數(shù)之一另外一個回調(diào)函數(shù)是 this.cb,這個函數(shù)只有在用 vm.$watch 生成的 Watcher 才會運行。
getter 在 expOrFn 是字符串時,會運行 parsePath 取得其對應(yīng)的在 Vue 實例對象上的一個熟悉。

其中 get 函數(shù)的職責(zé)就是執(zhí)行 getter 函數(shù)并將可能的返回值(如果該 Watcher 是renderWatcher 則返回值是 undefined)賦值給該 Watcher 的 value 屬性。

update 函數(shù)則是在一個 Dep 通過 notify 函數(shù)通知 Watcher 后執(zhí)行的函數(shù),在其中執(zhí)行了 run 函數(shù),run 中又執(zhí)行了 get 函數(shù)。

depend 則是用來將 Watcher 中 deps 數(shù)組保存的所有依賴進行關(guān)聯(lián),使這些依賴在 notify 時可以通知到該 Watcher。

defineReactive

位置: src/core/observer/index.js

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

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set

  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        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
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })
}

這個函數(shù)主要的職責(zé)是建立起某個對象屬性的get 和 set 鉤子,并且通過 observe 函數(shù)來獲取該對象的 Observer 對象,新建一個數(shù)據(jù)依賴 Dep。 在 get 鉤子函數(shù)中則去處理數(shù)據(jù)依賴和 Watcher 的關(guān)聯(lián),在 set 中調(diào)用依賴的 notify 函數(shù)通知關(guān)聯(lián)的 Watcher 去運行回調(diào)函數(shù)。

總結(jié)

到現(xiàn)在為止,已經(jīng)差不多理清了 Vue 數(shù)據(jù)綁定的大體邏輯,但是仍然還有很多遺留問題,比如:Dep 和 Watcher 是怎么建立聯(lián)系的,不同的 Watcher 都是在何時建立的,Watcher 的 getter 函數(shù)具體都有哪些,這些問題會在以后的文章中詳細(xì)說明。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容