Observer、Dep、Watcher 傻傻搞不清楚

4,198 阅读1分钟

最近又回过头来看 vue 的响应式原理,发现越看越迷了,ObserverDepWatcher 三者的作用是什么,他们的的关系究竟是怎样的呢?

Vue 初始化

我觉得搞清楚这些,首先要知道 vue 初始化的过程。我们从 new Vue() 开始,构造函数会执行 this._init,在 _init 中会进行合并配置、初始化生命周期、事件、渲染等,最后执行 vm.$mount 进行挂载。

// src/core/instance/index.js
function Vue (options) {
    // ...
    this._init(options)
}

// src/core/instance/init.js
Vue.prototype._init = function (options?: Object) {
    // 合并配置
    // ...
    
    // 一系列初始化
    // ...
    initState(vm)
    // ...
    
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
}

这里主要来看 initState(vm),响应式的核心均在于此,会进行 propsdatacomputedwatch 的初始化操作。

// src/core/instance/state.js
export function initState (vm: Component) {
    vm._watchers = []
    const opts = vm.$options
    if (opts.props) initProps(vm, opts.props) // 初始化 props
    // ...
    if (opts.data) {
        initData(vm) // 初始化 data
    } else {
        observe(vm._data = {}, true /* asRootData */)
    }
    // ...
}

了解了初始化过程,接下来就引出 Observer

Obserber 「数据的观察者」

在上面的 initData 中会执行 observe 方法进而实例化 ObserverObserver 的实例化过程就是递归地把 data 对象和子对象添加 __ob__ 属性同时通过我们熟知的 defindReactive 为属性定义 getter/setter

那么 Observer 顾名思义是观察者,观察的就是 data,它通过数据劫持使 data 的读写都处于它的监管之下。那么在观察到数据发生变化时会做出怎样的操作呢?

来到 defindReactive 的核心代码,会看到 getter 里的 dep.depend()setter 里的 dep.notify(),这就是依赖收集和触发更新的起点。这里的 depdefindReactive 内定义的一个常量,getter/setter 函数内持有对它的闭包引用,Dep 就是引出的下一个概念。

// src/core/observer/index.js
// ...
const dep = new Dep()
// ...
Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
        // ...
        dep.depend()
        //...
    },
    set: function reactiveSetter (newVal) {
        // ...
        dep.notify()
    },

Dep 「依赖管理」

先看下 Dep 类的定义

// src/core/observer/dep.js
export default class Dep {
    constructor () {
        this.id = uid++
        this.subs = []
    }

    addSub (sub: Watcher) {}

    removeSub (sub: Watcher) {}

    depend () {}
    
    notify () {}
}

可以看到 Dep 有一个实例属性 subs 数组,实例方法 addSub/removeSub 添加和删除数组中的某项。由此可以确定 dep 实例并非依赖而是依赖的管理者,subs 数组即为依赖(订阅者)列表,它们就是接下来登场的 Watcher

Watcher 「订阅者」

从上面知道 Observer Dep 都已经在 initState 中实例化了,响应式数据和依赖管理都准备好了,接下来就需要 Wacther 来订阅了。那么 Wacther 什么时候实例化呢,回到开头的初始化过程最后 vm.$mount 挂载,在这之后会执行 mountComponent 方法,Watcher 就是在这里实例化的(暂不关注 computed watcher 和 watch 选项的 watcher)。

// src/core/instance/lifecycle.js
export function mountComponent () {
    // ...
    new Watcher(vm, updateComponent, noop, {
        // ...
    }, true /* isRenderWatcher */)
}

Watcher 的定义如下,实例化时会执行 get 方法对传入的 updateComponent 进行求值,updateComponent 也就是 _render 函数,执行 _render 函数会读取 data 数据从而触发 getter 进行依赖收集。

// src/core/observer/watcher.js
export default class Watcher {
    
    // 对 getter 求值,进行依赖收集
    get () {}
    
    // 触发更新
    update() {}
}

总结

至此,我们可以总结一下三者的关系:

  • Observer 将数据定义为响应式,每个 Observer 实例都有自己的 Dep 来管理依赖。实例化 Wacther 的时候进行求值会触发 getter ,进而执行 dep.depend() 将当前 Wacther 加入 Dep 维护的依赖列表,这就是依赖收集过程。
  • 数据发生变化触发 setter 执行 dep.notifyDep 会执行所有依赖的 update 方法并加入异步更新队列,这就是触发依赖过程。