大宇宇宇
发布于 2025-08-31 / 12 阅读
0
0

computed是怎么做响应式的

在 Vue 中,computed 属性通过依赖追踪缓存机制实现高效的响应式更新。其核心原理可分解为以下步骤:


1. 初始化阶段

当 Vue 实例化时,会遍历 computed 选项,为每个计算属性创建一个内部 watcher(称为 computed watcher)。与普通 watcher 不同:

  • 惰性求值computed watcher 不会立即执行计算函数。

  • 缓存标记:内部维护 dirty 标志位,表示缓存是否失效。

// 伪代码示例(Vue 2.x)
function initComputed(vm) {
  const computed = vm.$options.computed;
  for (const key in computed) {
    const getter = typeof computed[key] === 'function' ? computed[key] : computed[key].get;
    // 为每个计算属性创建 watcher
    vm._computedWatchers[key] = new Watcher(vm, getter, { lazy: true });
    // 将计算属性代理到实例上
    defineComputed(vm, key, getter);
  }
}

2. 依赖收集(当访问计算属性时)

当组件渲染或代码中访问计算属性时(如 this.fullName),会触发其 getter:

  1. 检查缓存:若 dirtyfalse,直接返回缓存值。

  2. 重新计算:若 dirtytrue

    • 执行计算函数(如 return this.firstName + this.lastName)。

    • 关键步骤:在执行过程中,访问的响应式数据(如 firstName)会触发它们的 getter,从而将当前 computed watcher 添加到这些数据的依赖列表中(Dep.target 机制)。

    • 计算完成后,将结果缓存,并将 dirty 设为 false

// 计算属性的 getter 伪代码
function computedGetter() {
  const watcher = this._computedWatchers[key];
  if (watcher.dirty) {
    // 执行计算函数,同时收集依赖
    watcher.evaluate(); // 内部调用 watcher.get()
  }
  // 将当前 computed watcher 作为依赖(用于后续触发更新)
  if (Dep.target) {
    watcher.depend();
  }
  return watcher.value;
}

3. 依赖更新(当响应式数据变化时)

当计算属性依赖的响应式数据(如 firstName)被修改时:

  1. 通知依赖:数据的 setter 会通知所有依赖它的 watcher(包括 computed watcher)。

  2. 标记缓存失效computed watcherdirty 设为 true,但不立即重新计算。

  3. 触发组件更新:如果计算属性在模板中被使用,组件的渲染 watcher 会收到通知,触发重新渲染。

  4. 重新计算:在重新渲染过程中再次访问计算属性时,由于 dirtytrue,会重新执行计算函数并更新缓存。

// 响应式数据 setter 伪代码
function reactiveSetter(newValue) {
  const value = this.value;
  if (newValue !== value) {
    this.value = newValue;
    // 通知所有依赖该数据的 watcher
    dep.notify(); // 触发 watcher.update()
  }
}

// computed watcher 的 update 方法
update() {
  // 仅标记 dirty,不立即计算
  this.dirty = true;
  // 通知依赖此计算属性的组件重新渲染
  if (this.lazy) {
    // 如果是 computed watcher,触发依赖它的渲染 watcher
    this.dep.notify();
  }
}

4. 缓存机制的优势

  • 避免重复计算:只要依赖数据未变化,多次访问计算属性直接返回缓存值。

  • 按需更新:依赖数据变化时,仅标记缓存失效,延迟到实际访问时才重新计算。

  • 性能优化:相比 methodscomputed 在依赖不变时不会重复执行函数。


Vue 3 的改进

在 Vue 3 中,computed 基于 Proxyeffect 实现,原理类似但更高效:

  1. 使用 ReactiveEffect 替代 Watcher,通过 track(收集依赖)和 trigger(触发更新)管理依赖。

  2. 利用 refreactive 包装计算结果,通过 get value() 触发依赖收集。

  3. 通过 scheduler 优化更新流程,避免不必要的计算。

// Vue 3 computed 简化原理
function computed(getter) {
  let dirty = true;
  let value;
  const effect = new ReactiveEffect(getter, () => {
    dirty = true; // 依赖变化时标记缓存失效
  });

  return {
    get value() {
      if (dirty) {
        value = effect.run(); // 重新计算
        dirty = false;
      }
      track(effect); // 收集依赖
      return value;
    },
  };
}

总结

步骤

关键动作

初始化

为每个 computed 创建惰性 watcher,设置 dirty: true

访问时

dirtytrue,执行计算函数并收集依赖;否则返回缓存。

依赖变化

标记 dirty = true,通知依赖此计算属性的组件重新渲染。

重新渲染

再次访问计算属性时,因 dirtytrue,重新计算并更新缓存。

这种机制确保了 computed 的高效响应式更新,同时通过缓存避免了不必要的计算开销。


评论