通过上一节的分析我们了解 Vue 会把普通对象变成响应式对象,响应式对象 getter 相关的逻辑就是做依赖收集,这一节我们来详细分析这个过程。

  1. const dep = new Dep();
  2. Object.defineProperty(obj, key, {
  3. enumerable: true,
  4. configurable: true,
  5. get: function reactiveGetter() {
  6. const value = getter ? getter.call(obj) : val;
  7. dep.depend();
  8. return value;
  9. },
  10. });

这段代码我们只需要关注 2 个地方,一个是通过 new Dep 实例化了一个 dep 容器,另一个在访问 get 的同时通过 dep.depend 收集依赖。

Dep

Dep 是整个 getter 依赖收集的核心,它的定义在 src/core/observer/dep.js 中。

  1. export default class Dep {
  2. static target: ?Watcher;
  3. id: number;
  4. subs: Array<Watcher>;
  5. constructor() {
  6. this.id = uid++;
  7. this.subs = [];
  8. }
  9. addSub(sub: Watcher) {
  10. this.subs.push(sub);
  11. }
  12. removeSub(sub: Watcher) {
  13. remove(this.subs, sub);
  14. }
  15. depend() {
  16. if (Dep.target) {
  17. Dep.target.addDep(this);
  18. }
  19. }
  20. notify() {}
  21. }

Dep 是一个 Class,它定义了一些属性和方法,这里需要特别注意的是它有一个静态属性 target,这是一个全局唯一 Watcher,这是一个非常巧妙的设计,因为在同一时间只能有一个全局的 Watcher 被计算,另外它的自身属性 subs 也是 Watcher 的数组。

Dep 实际上就是对 Watcher 的一种管理,Dep 脱离 Watcher 单独存在是没有意义的,为了完整地讲清楚依赖收集过程,我们有必要看一下 Watcher 的一些相关实现,它的定义在 src/core/observer/watcher.js 中。

Watcher

  1. export default class Watcher {
  2. vm: Component;
  3. expression: string;
  4. cb: Function;
  5. id: number;
  6. deep: boolean;
  7. user: boolean;
  8. lazy: boolean;
  9. sync: boolean;
  10. dirty: boolean;
  11. active: boolean;
  12. deps: Array<Dep>;
  13. newDeps: Array<Dep>;
  14. depIds: SimpleSet;
  15. newDepIds: SimpleSet;
  16. before: ?Function;
  17. getter: Function;
  18. value: any;
  19. constructor(vm: Component, expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean) {
  20. this.deps = [];
  21. this.newDeps = [];
  22. this.depIds = new Set();
  23. this.newDepIds = new Set();
  24. }
  25. }

这边主要阅读核心的代码,其他部分就不一一列举了。

Watcher 是一个 Class,在它的构造函数中,定义了一些和 Dep 相关的属性,其中 this.depsthis.newDeps 表示 Watcher 实例持有的 Dep 实例的数组。

this.depIdsthis.newDepIds 分别代表 this.deps this.newDepsid Set(这个 Set ES6 的数据结构,它的实现在 src/core/util/env.js 中)。那么这里为何需要有 2 个 Dep 实例数组呢,稍后我们会解释。

  1. export default class Watcher {
  2. get() {}
  3. addDep(dep: Dep) {}
  4. cleanupDeps() {}
  5. update() {}
  6. run() {}
  7. evaluate() {}
  8. depend() {}
  9. teardown() {}
  10. }

Watcher 还定义了一系列的原型的方法,和依赖收集相关的有 get、addDep cleanupDeps 方法等。

过程分析