// 这一步相当于做了这么一件事:this.subs.push(Dep.target)
// 即添加了 Watcher 订阅,addDep 是 Watcher 的方法
Dep.target.addDep(this);
};
// 通知更新
Dep.prototype.notify = function() {
// this.subs 的每一项都为一个 Watcher 实例
this.subs.forEach(function(sub) {
// update 为 Watcher 的一个方法,更新视图
// 没错,实际上这个方法最终会调用到 Compile 中的 updaterFn,
// 也即 new Watcher(vm, exp, callback) 中的 callback
sub.update();
});
};
// 在 Watcher 中调用
Dep.prototype.addSub = function(sub) {
this.subs.push(sub);
};
// 初始时引用为空
Dep.target = null;
也许看到这还是一脸懵逼,没关系,接着往下。大概有同学会疑惑,为什么要把添加 Watcher 订阅放在 getter 中,接下来我们来说说这 Watcher 和 Dep 的故事。
Watcher & Dep
先让我们回顾一下 Compile 做的事,解析 fragment,然后给相应属性添加订阅:new Watcher(vm, exp, cb)。new 了这个 Watcher 之后,Watcher 怎么办呢,就有了下面这样的对话:
Watcher:hey Dep,我需要订阅 exp 属性的变动。
Dep:这我可做不到,你得去找 exp 属性中的 dep,他能做到这件事。
Watcher:可是他在闭包中啊,我无法和他联系。
Dep:你拿到了整个 Hue 实例 vm,又知道属性 exp,你可以触发他的 getter 啊,你在 getter 里动些手脚不就行了。
Watcher:有道理,可是我得让 dep 知道是我订阅的啊,不然他通知不到我。
Dep:这个简单,我帮你,你每次触发 getter 前,把你的引用告诉 Dep.target 就行了。记得办完事后给 Dep.target 置空。
于是就有了上面 getter 中的代码:
// ...
get: function() {
// 是否是 Watcher 触发的
if (Dep.target) {
// 是就添加进来
dep.depend();
}
return val;
}
// ...现在再回头看看 Dep 部分的代码,是不是好理解些了。如此一来, Watcher 需要做的事情就简单明了了:
function Watcher(vm, exp, cb) {
this.$vm = vm;
this.cb = cb;
this.exp = exp;
this.depIds = new Set(); // 返回一个用于获取相应属性值的函数
this.getter = parseGetter(exp.trim());
// 调用 get 方法,触发 getter
this.value = this.get();
}
Watcher.prototype.get = function() {
const vm = this.$vm;
// 将 Dep.target 指向当前 Watcher 实例
Dep.target = this;
// 触发 getter
let value = this.getter.call(vm, vm);
// Dep.target 置空
Dep.target = null;
return value;
};
Watcher.prototype.addDep = function(dep) {
const id = dep.id;
if (!this.depIds.has(id)) {










