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

下面为实验代码,主要观察 Observer 后对象变化及响应式原理,这里需要打开浏览器的开发者模式(F12),勾选Dep断点会打开Dep类的depend和notify方法中的断点,这里需要调整一下 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 属性。

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

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

当数据data修改时会调用setter方法此时会调用对应字段Dep.notify方法,对应的会调用Watcher.update方法。而update方法最终通过异步队列的方式执行Watcher.run方法,这部分读者可结合源码与断点执行情况学习了解。几个比较重要的细节有:
- 异步队列使用了nextTick
- 同步多次触发一个Watcher的update只会进一次执行队列
Watcher.run方法会再次调用Watcher.get完成新依赖的收集

# 数组
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也会被触发。
data的obj字段的getter/setter的depdata.obj值的__ob__.depdata.obj[0]值的__ob__.depdata.obj[1]值的__ob__.depdata.obj[1][0]值的__ob__.dep

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