Skip to content

Dep 类(事件中心)

javascript
subs;
addSub(sub);
notify();

Dep 类代表了一种依赖关系或订阅关系。每个响应式数据对象的每个属性都对应一个 Dep 实例。 Dep 实例的主要作用是:

收集依赖 (订阅者/观察者/watcher 对象)

  1. **当组件依赖于某个响应式数据属性时,会将组件对应的 Watcher 实例添加到该属性对应的 Dep 实例的 subs 数组中。**这就建立了组件与数据属性之间的依赖关系。
  2. 翻译一下:看哪些组件( watcher)依赖了(用到了) 当前的响应式数据属性

发布通知

  1. 当响应式数据属性发生变化时,属性对应的 Dep 实例会通过调用 notify 方法,遍历它的 subs 数组,通知所有依赖于该属性的 Watcher 实例进行视图更新

总的来说,Dep 类是一个状态管理器,用于管理依赖这个状态的订阅者。

javascript
class Dep {
  constructor() {
    /* 用来存放 Watcher 对象的数组 */
    this.subs = [];
  }

  /* 在 subs 中添加一个 Watcher 对象 */
  // 添加订阅者 - 收集依赖
  addSub(sub) {
    this.subs.push(sub);
  }

  /* 通知所有 Watcher 对象更新视图 */
  // 发布通知 - 通知所有订阅者
  notify() {
    this.subs.forEach((sub) => {
      sub.update();
    });
  }
}

// 订阅者(观察者) 指的是Watcher对象,Vue中指组件实例

这个 Dep 类实现了发布订阅模式的基本功能。它充当了一个中介的角色,负责管理订阅者(Watcher 对象)和发布者(数据变化)之间的关系。当数据发生变化时,对应的 Dep 实例会通知所有订阅了它的 Watcher 对象进行更新。

在 Vue.js 的响应式系统中, 每个数据对象的每个属性都对应一个 Dep 实例当组件渲染时,会生成对应的 Watcher 对象,并将它们添加到相应数据属性的 Dep 实例中。 当数据属性发生变化时,对应的 Dep 实例会通知所有订阅了它的 Watcher 对象,从而触发组件的重新渲染。

javascript
// 发布者 - 目标
class Dep {
  constructor() {
    // 记录所有的订阅者(观察者)
    this.subs = [];
  }

  // 添加订阅者
  addSub(sub) {
    // 注意,sub这个对象具有update方法,无需判断
    this.subs.push(sub);
  }

  removeSub(sub) {
    const index = this.subs.indexOf(sub);
    if (index !== -1) {
      this.subs.splice(index, 1);
    }
    // 源码中 这么写的
    // this.subs[this.subs.indexOf(sub)] = null;
  }

  // 发布通知 - 通知所有观察者
  notify(...args) {
    this.subs.forEach((sub) => {
      sub.update(...args);
    });
  }
}

// 2. 定义具体的观察者(订阅者)
class Watcher {
  constructor(name) {
    this.name = name;
  }

  update(...args) {
    // 数据变化更新视图
    console.log(`观察者 ${this.name} 收到通知,附加信息: ${args.join(", ")}`);
  }
}

// 发布者,将记录所有的观察者
const dep = new Dep();

const watcher1 = new Watcher("观察者1");
const watcher2 = new Watcher("观察者2");
const watcher3 = new Watcher("观察者3");

dep.addSub(watcher1);
dep.addSub(watcher2);
dep.addSub(watcher3);

dep.notify("状态发生改变", 123);

Dep 类 => 事件中心

Dep 类扮演了连接数据(发布者)和视图(订阅者)的中间件角色。具体来说,

  1. 订阅
    1. 当组件实例化时,会创建与之对应的 Watcher 实例。每个 Watcher 实例在执行依赖收集过程中,会读取它所依赖的响应式数据对象的属性,从而触发属性的 getter 函数。在 getter 中,会调用 dep.depend() 方法,将当前 Watcher 实例订阅到对应属性的 Dep 实例中(添加到 Dep 实例的 subs 数组中)。这相当于订阅了该属性值变化的事件。
  2. 发布通知
    1. 当响应式数据发生变化时,会触发对应属性的 setter 方法。在 setter 中,会调用 dep.notify() 方法,遍历该属性对应的 Dep 实例中的所有订阅者(Watcher 实例),并执行它们的 update 方法。这相当于发布了该属性值变化的事件。
  3. 解耦 Dep 充当了发布者(数据对象)和订阅者(Watcher 实例)之间的中介,实现了两者的解耦。数据对象只需要管理好自身的 Dep 实例,在发生变化时通知 Dep 实例即可。而 Watcher 实例只需订阅感兴趣的 Dep 实例,无需直接与数据对象产生联系。
javascript
// Dep 充当了发布者(数据对象)和订阅者(Watcher 实例)之间的中介
// 上面表述不太准确,发布者角色其实是由Observer类充当
// 准确地说,发布者是 Observer,而不是数据对象本身。数据对象只是数据的载体,
// Observer 通过遍历数据对象的属性,并添加 getter/setter 拦截器,
// 从而监听数据变化, 发布通知。

因此, Dep 类像一个事件中心,它维护了一组订阅者(Watcher 实例)列表,负责事件(数据变化)的广播。 当有事件发生时,它会遍历列表,逐个通知订阅者执行相应的操作(重新渲染视图)。这种设计模式使得发布者和订阅者之间解耦,也使系统具有良好的可扩展性。 需要注意的是,Vue 2.x 的响应式系统中每个响应式数据属性都对应一个 Dep 实例,因此可以看作是一种细粒度的发布订阅模式。这与传统的全局事件总线(Event Bus)有所不同,后者一般是整个应用程序级别的事件中心。

Watcher 类(订阅者)

Watcher 类代表了一种订阅者或观察者。 每个组件实例都对应一个 Watcher 实例,Watcher 实例会订阅它所依赖的响应式数据属性对应的 Dep 实例。 Watcher 实例的主要作用是:

订阅数据

  1. 在组件渲染过程中,Watcher 实例会读取它所依赖的响应式数据属性,从而触发这些属性对应的 Dep 实例的 addSub 方法,将自身添加为订阅者。(订阅数据的变化)

更新视图

  1. 当被订阅的响应式数据属性发生变化时,相应的 Dep 实例会通知所有订阅它的 Watcher 实例调用 update 方法,从而触发组件的重新渲染,使视图与数据保持同步。

总的来说,Watcher 类是一个观察者,它观察着被依赖的数据,当数据变化时,会被通知去更新视图。


PS: 依赖收集 实际上是把 Watcher 实例作为一个"依赖"添加到了用到当前数据对应的 Dep 实例中。这样当数据发生变化时, Dep 实例就能通知到所有"依赖"它的 Watcher 实例。

可以概括为以下两点:

  • 组件渲染时,会读取所需的响应式数据,触发 getter
  • 在 getter 中,会把当前渲染的 Watcher 添加到对应数据的 Dep 实例上,完成依赖收集

这种方式实现了:

  1. 精确捕获 watcher 所依赖的数据
  2. 建立数据属性与其依赖的 watcher 之间的联系

所以当数据发生变化时,对应的 Dep 就可以准确地通知到依赖自己的那些 Watcher,从而有效地触发组件或计算属性的重新渲染或计算。这是 Vue 响应式系统实现高效更新的关键所在。

Observer 类(发布者)

数据劫持:

  • 递归的遍历数据对象的所有属性,使用 Object.defineProperty 为每个属性添加 gette 和 setter

依赖收集

  • 当访问数据对象的某个属性时(模板用到了某个数据), 会触发属性的 getter 方法,此时 Observer 会调用 dep.depend将当前组件实例(Watcher 对象)收集为该属性的订阅者
typescript
// Dep类
class Dep {
  addSub(sub: DepTarget) {
    this.subs.push(sub);
  }
  depend(info?: DebuggerEventExtraInfo) {
    // Dep.target 当前watcher实例
    if (Dep.target) {
      Dep.target.addDep(this);
    }
  }
}

// Watcher类
class Watcher {
  addDep(dep: Dep) {
    const id = dep.id;
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id);
      this.newDeps.push(dep);
      if (!this.depIds.has(id)) {
        // 最终调用的还是dep.addSub(this)
        dep.addSub(this);
      }
    }
  }
}

派发更新

  • 当数据发生变化时,会触发属性的 setter 方法, 调用dep.notify通知该属性所有订阅的 Watcher 对象,从而触发组件的重新渲染。

Compiler 类

• 进行编译模板,并解析内部指令与插值表达式。 • 进行页面的首次渲染 • 数据变化后,重新渲染视图