# 响应式原理

响应式原理涉及三个对象src\core\observer\index.ts中的Observersrc\core\observer\dep.ts中的Depsrc\core\observer\watcher.ts中的Watcher,其关系为Observer->Dep<->Watcher

Observer、Dep、Watcher关系图

下面为实验代码,主要观察 Observer 后对象变化及响应式原理,这里需要打开浏览器的开发者模式(F12),勾选Dep断点会打开Dep类的dependnotify方法中的断点,这里需要调整一下 Call Stack 来观察 Scope 中变量的值。

# 对象

import("../vue.study.esm.js").then((module) => {
  const { Observer, Watcher } = module;
  const vm = {
    _watchers: [],
    _data: {
      obj: {
        a: 2,
      },
    },
  };
  const { _data: data } = vm;
  const ob = new Observer(data);
  const watcher = new Watcher(vm, () => data.obj.a, console.log, {}, true);
  data.obj = {
    a: 3,
  };
});
断点

如图Observer在对象下添加__ob__属性同时将属性字段转换为 getter/setter 属性。

Observer后的obj

Watcher中在执行由expOrFn而来的Watcher.get时通过设置Dep.target并访问Observer的相关字段时收集对应的Dep依赖,如果配置deep时会使用traverse遍历expOrFn返回结果的所有属性。

Watcher中收集Dep的调用链

下图为执行() => data.obj.a收集到的依赖,包括字段getter/setter闭包的Dep和字段值对应__ob__Dep。具体为

  1. dataobj字段的getter/setterdep
  2. data.obj值的__ob__.dep
  3. data.obja字段的getter/setterdep

Watcher中收集Dep的结果

当数据data修改时会调用setter方法此时会调用对应字段Dep.notify方法,对应的会调用Watcher.update方法。而update方法最终通过异步队列的方式执行Watcher.run方法,这部分读者可结合源码与断点执行情况学习了解。几个比较重要的细节有:

  1. 异步队列使用了nextTick
  2. 同步多次触发一个Watcher的update只会进一次执行队列
  3. Watcher.run方法会再次调用Watcher.get完成新依赖的收集

数据改变时触发Watcher 数据改变时触发Watcher

# 数组

import("../vue.study.esm.js").then((module) => {
  const { Observer, Watcher } = module;
  const vm = {
    _watchers: [],
    _data: {
      obj: [{ a : 1 }, [{ a : 2 }]],
    },
  };
  const { _data: data } = vm;
  const ob = new Observer(data);
  const watcher = new Watcher(vm, () => data.obj, console.log, {}, true);
  data.obj[0].a = -1; // 这里将调用a字段的getter,其dep并未收集到watcher
  data.obj[1].unshift({ a: 3 }); // 这里将通过data.obj[1].__ob__.dep触发的notify
});
断点

下图为依赖收集结果,可以看到虽然只访问了数组,但是实际数组内所有对象值的依赖也被收集,即当访问值为数组时依赖收集会下探所有子数组元素项。如果对数组元素对象使用$set/$delete强制触发notify相应Watcher也会被触发。

  1. dataobj字段的getter/setterdep
  2. data.obj值的__ob__.dep
  3. data.obj[0]值的__ob__.dep
  4. data.obj[1]值的__ob__.dep
  5. data.obj[1][0]值的__ob__.dep

Watcher中收集Dep的结果

在调用数组特定方法时会触发数组对象__ob__.dep.notify