Vue_MVVM 核心模块解析 - zen0822/interview GitHub Wiki

Vue MVVM 核心模块解析

图解

MVVM 图片概览

观察者模式(Observer, Watcher, Dep)

Vue 实现响应式有三个很重要的类,Observer类,Watcher类,Dep类

Dep 是依赖,Watcher 收集 Dep

  • Observer 类主要用于给Vue的数据defineProperty增加getter/setter方法,并且在getter/setter中收集依赖或者通知更新
  • Watcher 类来用于观察数据(或者表达式)变化然后执行回调函数(其中也有收集依赖的过程),主要用于$watch API和指令上
  • Dep 类就是一个可观察对象,可以有不同指令订阅它(它是多播的)

观察者模式,跟发布/订阅模式有点像 但是其实略有不同,发布/订阅模式是由统一的事件分发调度中心,on 则往中心中数组加事件(订阅),emit 则从中心中数组取出事件(发布),发布和订阅以及发布后调度订阅者的操作都是由中心统一完成

但是观察者模式则没有这样的中心,观察者订阅了可观察对象,当可观察对象发布事件,则就直接调度观察者的行为,所以这里观察者和可观察对象其实就产生了一个依赖的关系,这个是发布/订阅模式上没有体现的。

如何实现观察者模式呢?

我们先看下面代码,下面代码实现了 Watcher 去订阅 Dep 的过程,Dep 由于是可以被多个 Watcher 所订阅的,所以它拥有着订阅者数组,订阅了它,就把 Watcher 放入数组即可。

class Dep {
  constructor () {
    this.subs = []
  }

  notify () {
    const subs = this.subs.slice()

    for (let i = 0; i < subs.length; i++) {
        subs[i].update()
    }
  }

  // 添加 Watcher
  addSub (sub) {
    this.subs.push(sub)
  }
}

class Watcher {
  constructor () {
  }

  update () {
  }
}

let dep = new Dep()
dep.addSub(new Watcher()) // Watcher订阅了依赖

我们实现了订阅,那通知发布呢,也就是上面的 notify 在哪里实现呢?

我们到这里就可以联系到数据响应,我们需要的是数据变化去通知更新,那显然是会在 defineProperty 中的 setter 中去实现了,聪明的你应该想到了,我们可以把每一个数据当成一个 Dep 实例,然后 setter 的时候去 notify 就行了,所以我们可以在 defineProperty 中 new Dep(),通过闭包 setter 就可以取到 Dep 实例了

在每一个可观察的对象中 get 中是可以取到这个 Dep 实例的,所以可以在执行 watch(订阅) 操作的时候,执行获取数值,触发 getter 去收集依赖

function defineReactive (obj, key, val) {
    const dep = new Dep()
    const property = Object.getOwnPropertyDescriptor(obj, key)

    const getter = property && property.get
    const setter = property && property.set

    let childOb = 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() // 触发当前依赖 Dep 加入 Dep.target(Watcher.addDep(this))

              if (childOb) {
                // 子元素变成了可观察者对象,Dep.target 触发变成当前的 Watcher 对象,
                // 所以需要将当前的 Dep.target 加入到这个可观察者对象
                childOb.dep.depend()

                if (Array.isArray(value)) {
                  dependArray(value)
                }
              }
            }

            return value
        },
        set: function reactiveSetter (newVal) {
            const value = getter ? getter.call(obj) : val

            if (newVal === value) {
                return
            }

            if (setter) {
                setter.call(obj, newVal)
            } else {
                val = newVal
            }

            childOb = !shallow && observe(newVal) // 看看新值是不是可以变成可观察对象的
            dep.notify()
        }
    })
}

结合 Watcher 模块

// 为Dep.target 赋值
function pushTarget (Watcher) {
  Dep.target = Watcher
}

function popTarget (Watcher) {
  Dep.target = null
}

class Watcher () {
  constructor (vm, expOrFn, cb, options) {
     //传进来的对象 例如Vue
     this.vm = vm

     //在 Vue 的 render 中 cb 是更新视图的核心,调用 diff 并更新视图的过程
     this.cb = cb

     //收集 Deps,用于移除监听
     this.newDeps = []

     // Dep 依赖的 getter 方法,也就是调用获取这个变量的值
     if (typeof expOrFn === 'function') {
       this.getter = expOrFn
     } else {
       this.getter = parsePath(expOrFn)
       if (!this.getter) {
         this.getter = function () {}
       }
     }

     //设置Dep.target的值,依赖收集时的watcher对象
     this.value =this.get()
  }

  get () {
    pushTarget(this) // 标记全局变量 Dep.target
    let value = this.getter.call(vm, vm) // 触发 getter,收集依赖(将观察者加入可观察对象)

    if (this.deep) {
      traverse(value)
    }

    popTarget() // 标记全局变量 Dep.target 为 null,将当前的依赖对象回退到上一个状态

    return value
  }

  // 订阅的 Dep 会 notify 通知 Watcher 更新值和执行回调函数
  update () {
    this.run()
  }

  run () {
      const value = this.get() // new Value

      // re-collect dep 重复收集依赖,看看新的值是不是观察者来的
      if (value !== this.value ||
          isObject(value)) {
          const oldValue = this.value
          this.value = value
          this.cb.call(this.vm, value, oldValue)
      }
  }
}