Object 的依赖收集和触发都是在 defineProperty 中进行的,因此 Dep 实例定义在 defineReactive 函数中就可以让 getter 和 setter 都拿到。
而对于 Array 来说,依赖可以在 getter 中收集,但触发却是在拦截器中,为了保证 getter 和 拦截器中都能访问到 Dep 实例,Vue 中给 Observer 实例上添加了 dep 属性。
class Observer {
constructor(value) {
this.value = value;
this.dep = new Dep();
def(value, '__ob__', this);
if (Array.isArray(value)) {
value.__proto__ = arrayMethods;
} else {
this.walk(value);
}
}
walk(obj) {
for (const [key, value] of Object.entries(obj)) {
defineReactive(obj, key, value);
}
}
}
Observer 在处理数据响应式时也将自身实例添加到了数据的 __ob__ 属性上,因此在 getter 和拦截器中都能通过响应式数据本身的 __ob__.dep 拿到其对应的依赖。修改 defineReactive 和 拦截器如下:
function defineReactive(data, key, val) {
let childOb = observe(val);
let dep = new Dep();
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
dep.depend();
// 给 Observer 实例上的 dep 属性收集依赖
if (childOb) {
childOb.dep.depend();
}
return val;
},
...
})
};[
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
].forEach(method => {
const original = ArrayProto[method];
def(arrayMethods, method, (...args) => {
const result = original.apply(this, args);
const ob = this.__ob__;
ob.dep.notify();
return result;
})
})
依赖长什么样
现在已经知道了依赖保存在每个响应式数据对应的 Dep 实例中的 subs 中,通过上面 Dep 的代码可以知道,收集的依赖是一个全局对象,且该对象对外暴露了一个 update 方法,记录了数据变化时需要进行的更新操作(如修改 dom 或 Vue 的 Watch)。
首先这个依赖对象的功能主要有两点:
需要主动将自己收集到对应响应式数据的 Dep 实例中;
保存数据变化时要进行的操作并在 update 方法中调用;
其实就是一个中介角色,Vue 中起名为 Watcher。
class Watcher {
constructor(vm, expOrFn, cb) {
this.vm = vm;
// 保存通过表达式获取数据的方法
this.getter = parsePath(expOrFn);
this.cb = cb;
this.value = this.get();
}
get() {
// 将自身 Watcher 实例挂到全局对象上
window.target = this;
// 获取表达式对应的数据
// 会自动触发该数据的 getter










