面试题:Vue 是如何收集依赖的?

Vue 的依赖收集是其响应式系统的核心机制之一,它通过依赖追踪来实现数据的自动更新。

具体来说,Vue 在初始化时会为每个响应式属性创建一个 Dep(依赖)对象,并在属性被访问时收集依赖(即 Watcher),在属性被修改时通知依赖更新。

以下是 Vue 收集依赖的详细过程:


1. 响应式数据的初始化

Vue 通过 Object.defineProperty(Vue 2)或 Proxy(Vue 3)将数据对象转换为响应式对象。

Vue 2 的实现

在 Vue 2 中,Vue 会遍历数据对象的每个属性,使用 Object.defineProperty 为每个属性定义 getter 和 setter

示例

function defineReactive(obj, key, val) {
  const dep = new Dep(); // 每个属性对应一个 Dep 对象

  Object.defineProperty(obj, key, {
    get() {
      if (Dep.target) {
        dep.depend(); // 收集依赖
      }
      return val;
    },
    set(newVal) {
      if (newVal === val) return;
      val = newVal;
      dep.notify(); // 通知依赖更新
    },
  });
}

说明

  • 每个属性都有一个对应的 Dep 对象,用于存储依赖(即 Watcher)。
  • 在 getter 中调用 dep.depend() 收集依赖。
  • 在 setter 中调用 dep.notify() 通知依赖更新。

Vue 3 的实现

在 Vue 3 中,Vue 使用 Proxy 实现响应式数据。

示例

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      const dep = getDep(target, key); // 获取或创建 Dep 对象
      if (Dep.target) {
        dep.depend(); // 收集依赖
      }
      return Reflect.get(target, key, receiver);
    },
    set(target, key, value, receiver) {
      const dep = getDep(target, key); // 获取或创建 Dep 对象
      const result = Reflect.set(target, key, value, receiver);
      dep.notify(); // 通知依赖更新
      return result;
    },
  });
}

说明

  • 使用 Proxy 拦截对象的 get 和 set 操作。
  • 在 get 中收集依赖,在 set 中通知依赖更新。

2. 依赖的收集

依赖的收集是通过 Watcher 实现的。Watcher 是一个观察者,它会在组件渲染或计算属性求值时被创建。

Watcher 的创建

  • 在组件渲染时,Vue 会创建一个 Watcher 来观察模板中用到的响应式数据。
  • 在计算属性求值时,Vue 也会创建一个 Watcher 来观察计算属性依赖的响应式数据。

示例

class Watcher {
  constructor(vm, expOrFn, cb) {
    this.vm = vm;
    this.getter = parsePath(expOrFn); // 解析表达式或函数
    this.cb = cb;
    this.value = this.get(); // 初始化时触发依赖收集
  }

  get() {
    Dep.target = this; // 将当前 Watcher 设置为全局的 Dep.target
    const value = this.getter.call(this.vm, this.vm); // 访问响应式数据,触发 getter
    Dep.target = null; // 重置 Dep.target
    return value;
  }

  update() {
    const oldValue = this.value;
    this.value = this.get(); // 重新获取值
    this.cb.call(this.vm, this.value, oldValue); // 执行回调
  }
}

说明

  • 在 Watcher 初始化时,会调用 get() 方法,访问响应式数据,触发 getter
  • 在 getter 中,Vue 会将当前 Watcher 添加到 Dep 中,完成依赖收集。

3. 依赖的更新

当响应式数据发生变化时,Vue 会通知所有依赖该数据的 Watcher 进行更新。

示例

class Dep {
  constructor() {
    this.subs = []; // 存储 Watcher
  }

  depend() {
    if (Dep.target) {
      this.subs.push(Dep.target); // 收集 Watcher
    }
  }

  notify() {
    this.subs.forEach(watcher => watcher.update()); // 通知 Watcher 更新
  }
}

说明

  • 在 setter 中,Vue 会调用 dep.notify(),通知所有 Watcher 更新。
  • Watcher 的 update() 方法会重新求值并执行回调。

4. 依赖收集的流程

  1. 初始化阶段
    • Vue 将数据对象转换为响应式对象,为每个属性创建 Dep 对象。
    • 在组件渲染或计算属性求值时,创建 Watcher。
  2. 依赖收集阶段
    • Watcher 访问响应式数据,触发 getter
    • 在 getter 中,Vue 将当前 Watcher 添加到 Dep 中。
  3. 依赖更新阶段
    • 响应式数据发生变化时,触发 setter
    • 在 setter 中,Vue 调用 dep.notify(),通知所有 Watcher 更新。

5. 总结

  • 依赖收集的核心:通过 getter 收集依赖,通过 setter 通知依赖更新。
  • Dep:每个响应式属性对应一个 Dep 对象,用于存储 Watcher。
  • Watcher:观察者,负责观察响应式数据并在数据变化时执行更新。
  • 流程
    1. 初始化响应式数据。
    2. 在访问数据时收集依赖。
    3. 在数据变化时通知依赖更新。

通过这种机制,Vue 实现了数据的自动更新,确保视图与数据保持同步。

THE END
点赞12 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容