Vue 2.5 数据绑定实现逻辑(二)Dep,Observer 和 Watcher 的协同配合

上一节说到了 Dep, Observer, Watcher 这三个对象的作用,即 Dep 是数据依赖,Observer 用来处理成员对象的增删,Watcher 负责在数据发生变动的时候执行回调函数。

这一节则主要来说:

  1. Watcher 何时将 Dep 添加到自己的 deps 数组中。
  2. Dep 何时将依赖于自身的 Watcher 添加到自身的 subs 数组中
  3. Observer 中的 Dep 什么时候将相关的 Watcher 添加到自身的 subs 数组中

Dep 和 Watcher 的 depend 方法及全局 Watcher 栈

全局 Watcher 栈

在 src/core/observer/dep.js 中,存在一个全局的栈,用来储存当前被触发且正在运行的 Watcher,同时 Dep 还有一个静态成员 target ,在这里可以看成是栈顶元素。

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()
}
Dep 的 depend 方法

Dep 的 depend 方法是从栈中获取栈顶的 Watcher 并添加到自己的 subs 数组中。
其中运行了 Watcher 的 addDep 来将该 Dep 添加到 Watcher 的 newDeps 数组中(之后会进行处理,就会将所有新的依赖添加到真正的 deps 数组中,这里的 newDeps 是起缓冲的作用的,而这个缓冲起了什么作用,我还有待研究),在这个 addDep 数组中又运行了 Dep 对象的 addSub 方法来将该 Watcher 添加到自己的 subs 数组中, 形成一种你中有我,我中有你的状态。

addSub (sub: Watcher) {
    this.subs.push(sub)
  },
...
depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }
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)
      }
    }
  }
Watcher 的 depend 方法

Watcher 的 depend 方法是运行 deps 数组中所有的 Dep 对象的 depend 方法,比较好理解。

depend () {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }

全局 Watcher 栈何时执行压栈和出栈

上节中说到 Watcher 在建立的时候会接收并保存一个 getter 函数, 在 Watcher 的 get 函数中则会运行这个 getter 并获取可能的返回值赋值给 value。

在这段代码中可以看到,在执行 getter 之前,运行了 pushTarget(this) 来将自己(现在正在运行的 Watcher)压入全局 Watcher 栈,而在 getter 执行结束后运行 popTarget 执行出栈操作。

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
  }

defineReactive,get 和 set 钩子函数

在 defineReactive 函数中,首先做了两个比较重要的操作:

  1. 新建一个 Dep 对象,通过闭包将其保存下来。
  2. 对该 value 执行 observe 函数,递归建立起 value 及其以下所有需要建立的 Observer 对象,同时为所有属性运行 defineReactive 函数设置钩子。
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)
  ......
}

接下来就是建立钩子函数。

在 get 中,主要检测全局 Watcher 栈的栈顶是否有元素,如果有则运行之前建立的 Dep 对象的 depend 来建立该依赖和 Watcher 的关联关系, 同时建立该 value 的 Observer 中的 Dep 对象和 Watcher 的关联关系(如果该 value 是对象或者数组)。 之前说过,只有在某个 Watcher 的 getter 函数中在运行中的时候(中间几个语句的执行可以忽略)才会将这个 Watcher 压栈且其出于栈顶位置,所以在这个时候如果运行了 get 钩子函数,则基本可以肯定此时运行的 getter 函数是和这个 value 有关系的,即这个依赖和栈顶的 Watcher 有关联。

在 set 中, 首先检测新值和旧值是否相等,如果相等则直接返回。之后又运行了一遍 observe 函数,因为赋的新值有可能是一个数组或者对象,即需要建立新的一个或多个 Observer。最后运行了 notify 来通知这个依赖相关联的所有 Watcher 去运行回调函数。

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

vm.$set 和 vm.$delete

Vue 实例对象的 $set 方法对应的是 set 方法, $delete 对应的是 del。

// 位置: src/core/instance/state.js
Vue.prototype.$set = set
Vue.prototype.$delete = del

逻辑其实很简单,就是给一个对象添加成员时先检测这个对象是不是 Vue 实例,是的话则直接返回(set 了也没用)。之后获取该对象的 Observer 并运行其中 Dep 对象的 notify 方法通知 Watcher,还要设置好新成员的钩子函数。删除对象成员时也是相似了逻辑。

// 位置: src/core/observer/index.js
export function set (target: Array | Object, key: any, val: any): any {
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    target.splice(key, 1, val)
    return val
  }
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  const ob = (target: any).__ob__
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    )
    return val
  }
  if (!ob) {
    target[key] = val
    return val
  }
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}
......
export function del (target: Array | Object, key: any) {
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.splice(key, 1)
    return
  }
  const ob = (target: any).__ob__
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid deleting properties on a Vue instance or its root $data ' +
      '- just set it to null.'
    )
    return
  }
  if (!hasOwn(target, key)) {
    return
  }
  delete target[key]
  if (!ob) {
    return
  }
  ob.dep.notify()
}

总结

简单来说,就是 Dep 和 Watcher 在 get 钩子函数中建立关联(实际上在初始化计算属性的时候也建立了关联,但是放在以后说), 这个过程中有一个全局的 Watcher 栈做辅助,可以说这个设计相当巧妙。在 set 钩子函数和调用 vm.$set 和 vm.$delete 时运行 notify 来通知 Watcher。

你可能感兴趣的:(Vue 2.5 数据绑定实现逻辑(二)Dep,Observer 和 Watcher 的协同配合)