核心代码
//Dep类用来收集依赖、删除依赖、通知依赖更新
class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
notify() {
this.subs.forEach(sub => sub.update());
}
removeSub(sub) {
const index = this.subs.indexOf(sub);
if (index > -1) {
this.subs.splice(index, 1);
}
}
}
// Observer类将data对象内的所有属性(包括子属性)都转换成getter/setter的形式,所以可侦测每个属性的变化
class Observer {
constructor(data) {
this.data = data;
if (!Array.isArray(data)) {
this.walk(data);
}
}
walk(obj) {
Object.keys(obj).forEach(key => {
this.defineReactive(obj, key, obj[key]);
});
}
defineReactive(data, key, val) {
if (typeof val === 'object') { //递归子属性
new Observer(val);
}
let dep = new Dep();
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
dep.addSub(window.target);
return val;
},
set(newValue) {
if (newValue === val) {
return;
}
val = newValue;
dep.notify();
}
});
}
}
//Watcher实例即为上面所说的依赖,也就是我们常说的订阅者
class Watcher {
constructor(vm, exp, cb) {
this.vm = vm; // ViewModel 的实例
this.exp = exp; // 要观察的数据属性的表达式(即字符串)
this.cb = cb; // 当数据属性变化时要调用的回调函数
this.value = this.get(); // 初始化时获取数据属性的值
}
get() {
window.target = this; // 临时将当前 Watcher 实例设置为全局目标
const value = this.vm[this.exp]; // 使用 exp 从 vm 中获取属性值(此时会触发getter)
window.target = null; // 清除全局目标
return value; // 返回获取到的属性值
}
update() {
const value = this.get();//获取新值
// 只有当新值和旧值不同时才调用回调函数并更新旧值
if (value !== this.value) {
this.value = value;
this.cb.call(this.vm, value);
}
}
}
Watcher原理
先把自己设置到全局唯一的指定位置(如window.target),然后读取数据。因为读取了数据,所以会触发这个数据的getter。接着,在getter中就会从全局指定位置获取当前正在读取数据的Watcher实例,并把这个Watcher实例收集到Dep中。通过这样的方式,Watcher可以主动去订阅任意一个数据的变化。
总结
data通过Observer转换成了getter/setter的形式来追踪变化。
当外界通过watcher读取数据时,会触发getter从而将watcher实例(依赖)添加到该数据的专属Dep实例中。
当数据发生变化时,会触发该数据的setter,从而向Dep中的依赖发送通知。watcher接收到通知后,会向外界发送通知,变化通知到外界后可能会触发试图更新,也有可能触发用户的某个回调函数(调用Dep中的notify方法,遍历执行所有watcher实例上的update方法)。