通过上一节的分析我们了解 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 方法等。
