通过上一节的分析我们了解 Vue 会把普通对象变成响应式对象,响应式对象 getter 相关的逻辑就是做依赖收集,这一节我们来详细分析这个过程。
const dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
const value = getter ? getter.call(obj) : val;
dep.depend();
return value;
},
});
这段代码我们只需要关注 2 个地方,一个是通过 new Dep 实例化了一个 dep 容器,另一个在访问 get 的同时通过 dep.depend 收集依赖。
Dep
Dep 是整个 getter 依赖收集的核心,它的定义在 src/core/observer/dep.js 中。
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor() {
this.id = uid++;
this.subs = [];
}
addSub(sub: Watcher) {
this.subs.push(sub);
}
removeSub(sub: Watcher) {
remove(this.subs, sub);
}
depend() {
if (Dep.target) {
Dep.target.addDep(this);
}
}
notify() {}
}
Dep 是一个 Class,它定义了一些属性和方法,这里需要特别注意的是它有一个静态属性 target,这是一个全局唯一 Watcher,这是一个非常巧妙的设计,因为在同一时间只能有一个全局的 Watcher 被计算,另外它的自身属性 subs 也是 Watcher 的数组。
Dep 实际上就是对 Watcher 的一种管理,Dep 脱离 Watcher 单独存在是没有意义的,为了完整地讲清楚依赖收集过程,我们有必要看一下 Watcher 的一些相关实现,它的定义在 src/core/observer/watcher.js 中。
Watcher
export default class Watcher {
vm: Component;
expression: string;
cb: Function;
id: number;
deep: boolean;
user: boolean;
lazy: boolean;
sync: boolean;
dirty: boolean;
active: boolean;
deps: Array<Dep>;
newDeps: Array<Dep>;
depIds: SimpleSet;
newDepIds: SimpleSet;
before: ?Function;
getter: Function;
value: any;
constructor(vm: Component, expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean) {
this.deps = [];
this.newDeps = [];
this.depIds = new Set();
this.newDepIds = new Set();
}
}
这边主要阅读核心的代码,其他部分就不一一列举了。
Watcher 是一个 Class,在它的构造函数中,定义了一些和 Dep 相关的属性,其中 this.deps 和 this.newDeps 表示 Watcher 实例持有的 Dep 实例的数组。
而 this.depIds 和 this.newDepIds 分别代表 this.deps 和 this.newDeps 的 id Set(这个 Set 是 ES6 的数据结构,它的实现在 src/core/util/env.js 中)。那么这里为何需要有 2 个 Dep 实例数组呢,稍后我们会解释。
export default class Watcher {
get() {}
addDep(dep: Dep) {}
cleanupDeps() {}
update() {}
run() {}
evaluate() {}
depend() {}
teardown() {}
}
Watcher 还定义了一系列的原型的方法,和依赖收集相关的有 get、addDep 和 cleanupDeps 方法等。