Learning Record

2026年2月16日学习记录

记录于:2026-02-16

目录

  1. 响应式系统概览
  2. Ref 的实现原理
  3. Effect 的基础实现
  4. 依赖收集系统
  5. 调度器机制
  6. 核心概念总结

响应式系统概览

Vue3 的响应式系统基于 依赖追踪发布-订阅 模式,核心思想是:

  • 响应式数据(Ref/Reactive):数据变化时自动通知依赖
  • Effect:副作用函数,访问响应式数据时自动建立依赖关系
  • 双向链表:高效管理 Dependency(依赖项)和 Subscriber(订阅者)的关系

核心流程

1. 创建响应式数据 (ref/reactive)
2. 在 effect 中访问响应式数据
3. 建立依赖关系(依赖收集)
4. 数据变化时自动触发 effect 重新执行(派发更新)

Ref 的实现原理

1. RefImpl 类的结构

class RefImpl implements Dependency {
  _value; // 保存实际的值
  [ReactiveFlags.IS_REF] = true; // ref 标记
  subs: Link; // 订阅者链表的头节点
  subsTail: Link; // 订阅者链表的尾节点

  constructor(value) {
    // 如果 value 是对象,使用 reactive 转换为响应式
    this._value = isObject(value) ? reactive(value) : value;
  }
}

2. 核心访问器

getter - 依赖收集

get value() {
  // 如果有当前正在执行的 effect,收集依赖
  if (activeSub) {
    trackRef(this);
  }
  return this._value;
}

关键点:

  • 只有在 activeSub 存在时才收集依赖
  • activeSub 是当前正在执行的 effect 实例

setter - 派发更新

set value(newValue) {
  if (hasChanged(newValue, this._value)) {
    // 只有值真正改变时才触发更新
    this._value = isObject(newValue) ? reactive(newValue) : newValue;
    triggerRef(this);  // 触发依赖的 effect 重新执行
  }
}

关键点:

  • 使用 hasChanged 判断新旧值是否不同
  • 避免不必要的更新,提升性能

3. 依赖收集和触发

// 收集依赖
function trackRef(dep) {
  if (activeSub) {
    link(dep, activeSub); // 建立双向链表关系
  }
}

// 触发更新
function triggerRef(dep) {
  if (dep.subs) {
    propagate(dep.subs); // 从头节点开始遍历,通知所有订阅者
  }
}

Effect 的基础实现

1. ReactiveEffect 类

class ReactiveEffect implements Sub {
  active = true; // 表示 effect 是否激活
  deps: Link; // 依赖项链表的头节点
  depsTail: Link; // 依赖项链表的尾节点
  tracking = false; // 标记是否正在追踪依赖
  dirty = false; // 标记是否需要重新执行

  constructor(public fn) {}
}

2. run 方法 - Effect 执行核心

run() {
  // 如果 effect 已经被停止,直接执行函数不收集依赖
  if (!this.active) {
    return this.fn();
  }

  // 保存之前的 effect(处理嵌套 effect)
  const prevSub = activeSub;

  // 设置当前 effect 为活动状态
  setActiveSub(this);
  startTrack(this);  // 开始追踪依赖

  try {
    return this.fn();  // 执行用户函数,期间会触发响应式数据的 getter
  } finally {
    endTrack(this);    // 结束追踪,清理过期依赖
    setActiveSub(prevSub);  // 恢复之前的 effect
  }
}

关键设计:

  • 嵌套 effect 支持:通过保存和恢复 prevSub 实现
  • 依赖追踪范围:只在 startTrackendTrack 之间收集依赖
  • 自动清理endTrack 会清理不再需要的旧依赖

3. 通知和调度

// 通知方法:依赖数据变化时被调用
notify() {
  this.scheduler();  // 委托给调度器
}

// 调度器:默认直接执行,可自定义
scheduler() {
  this.run();  // 默认行为:直接重新执行
}

4. stop 方法 - 停止 Effect

stop() {
  if (this.active) {
    startTrack(this);  // 开始追踪
    endTrack(this);    // 立即结束,触发依赖清理
    this.active = false;  // 标记为不活跃
  }
}

5. effect 函数 - 创建 Effect

function effect(fn, options) {
  const e = new ReactiveEffect(fn);

  // 合并用户选项(如 scheduler)
  Object.assign(e, options);

  // 立即执行一次,收集依赖
  e.run();

  // 创建 runner 函数
  const runner = e.run.bind(e);

  // 将 effect 实例挂载到 runner 上,方便外部访问
  runner.effect = e;

  return runner;
}

为什么要定义 runner 变量?

不能写成 return () => e.run(),因为需要:

  1. 在返回的函数上挂载 effect 属性
  2. 让用户能通过 runner.effect 访问原始 effect 实例
  3. 例如:runner.effect.stop() 停止 effect
const runner = effect(() => {
  console.log("effect");
});

// 通过 runner 访问 effect 实例
runner.effect.stop(); // 停止 effect

依赖收集系统

1. 双向链表结构

Vue3 使用 双向链表 来管理依赖关系,这是一个核心设计:

interface Dependency {
  subs: Link; // 订阅者链表头节点
  subsTail: Link; // 订阅者链表尾节点
}

interface Sub {
  deps: Link; // 依赖项链表头节点
  depsTail: Link; // 依赖项链表尾节点
  tracking: boolean;
}

interface Link {
  sub: Sub; // 订阅者
  dep: Dependency; // 依赖项
  nextSub: Link; // 下一个订阅者
  prevSub: Link; // 上一个订阅者
  nextDep: Link; // 下一个依赖项
}

结构示意:

Dependency (ref/reactive 的属性)
    ↓ subs
   Link1 → Link2 → Link3  (订阅者链表)
    ↓       ↓       ↓
   Sub1    Sub2    Sub3   (effect 实例)
Subscriber (effect 实例)
    ↓ deps
   Link1 → Link2 → Link3  (依赖项链表)
    ↓       ↓       ↓
   Dep1    Dep2    Dep3   (响应式数据)
function link(dep, sub) {
  // 1. 尝试复用已有的链表节点
  const currentDep = sub.depsTail;
  const nextDep = currentDep === undefined ? sub.deps : currentDep.nextDep;

  if (nextDep && nextDep.dep === dep) {
    // 依赖未变化,直接复用
    sub.depsTail = nextDep;
    return;
  }

  // 2. 创建或复用节点
  let newLink;
  if (linkPool) {
    // 从对象池复用节点
    newLink = linkPool;
    linkPool = linkPool.nextDep;
    newLink.nextDep = nextDep;
    newLink.dep = dep;
    newLink.sub = sub;
  } else {
    // 创建新节点
    newLink = { sub, dep, nextDep, nextSub: undefined, prevSub: undefined };
  }

  // 3. 将节点链接到 dep 的订阅者链表
  if (dep.subsTail) {
    dep.subsTail.nextSub = newLink;
    newLink.prevSub = dep.subsTail;
    dep.subsTail = newLink;
  } else {
    dep.subs = newLink;
    dep.subsTail = newLink;
  }

  // 4. 将节点链接到 sub 的依赖项链表
  if (sub.depsTail) {
    sub.depsTail.nextDep = newLink;
    sub.depsTail = newLink;
  } else {
    sub.deps = newLink;
    sub.depsTail = newLink;
  }
}

优化亮点:

  • 节点复用:通过 linkPool 对象池复用节点,减少 GC 压力
  • 双向关联:同时建立 dep→sub 和 sub→dep 的关系
  • 尾节点优化:通过 depsTailsubsTail 实现 O(1) 的尾部插入

3. startTrack - 开始追踪

function startTrack(sub) {
  sub.tracking = true;
  sub.depsTail = undefined; // 重置尾节点,用于后续判断哪些依赖是新的
}

作用:

  • 标记开始收集依赖
  • 重置 depsTail,用于区分新旧依赖

4. endTrack - 结束追踪并清理

function endTrack(sub) {
  sub.tracking = false;
  const depsTail = sub.depsTail;
  sub.dirty = false;

  // 清理不再需要的旧依赖
  if (depsTail) {
    if (depsTail.nextDep) {
      clearTracking(depsTail.nextDep);
      depsTail.nextDep = undefined;
    }
  } else if (sub.deps) {
    clearTracking(sub.deps);
    sub.deps = undefined;
  }
}

清理逻辑:

  • depsTail 之后的节点是旧依赖(本次执行没有访问到)
  • 这些旧依赖需要被清理掉

5. clearTracking - 清理依赖

function clearTracking(link: Link) {
  while (link) {
    const { prevSub, nextSub, nextDep, dep } = link;

    // 从 dep 的订阅者链表中移除
    if (prevSub) {
      prevSub.nextSub = nextSub;
    } else {
      dep.subs = nextSub;
    }

    if (nextSub) {
      nextSub.prevSub = prevSub;
    } else {
      dep.subsTail = prevSub;
    }

    // 清空节点引用
    link.dep = link.sub = undefined;

    // 回收到对象池
    link.nextDep = linkPool;
    linkPool = link;

    link = nextDep;
  }
}

关键点:

  • 双向链表的删除操作
  • 将清理的节点回收到对象池
  • 避免内存泄漏

6. propagate - 传播更新

function propagate(subs) {
  let link = subs;
  let queuedEffect = [];

  while (link) {
    const sub = link.sub;

    // 只通知未被标记为 dirty 的 effect
    if (!sub.tracking && !sub.dirty) {
      sub.dirty = true;

      if ("update" in sub) {
        // 计算属性特殊处理
        processComputedUpdate(sub);
      } else {
        // 普通 effect 加入队列
        queuedEffect.push(sub);
      }
    }

    link = link.nextSub;
  }

  // 批量执行所有 effect
  queuedEffect.forEach((effect) => effect.notify());
}

优化策略:

  • 去重:通过 dirty 标记避免重复触发
  • 批量执行:先收集需要执行的 effect,再统一执行
  • 计算属性优先:有 update 方法的(computed)优先处理

调度器机制

1. 什么是调度器?

调度器(Scheduler)是 Vue3 响应式系统的核心特性,它允许自定义 effect 的执行时机和方式

2. 默认调度器

class ReactiveEffect {
  notify() {
    this.scheduler(); // 数据变化时调用
  }

  scheduler() {
    this.run(); // 默认行为:立即执行
  }
}

3. 自定义调度器

通过 options.scheduler 可以自定义执行逻辑:

const runner = effect(
  () => {
    console.log("effect run");
  },
  {
    scheduler: () => {
      console.log("自定义调度");
      // 可以控制何时执行 effect
    },
  }
);

4. 调度器的应用场景

场景 1:异步更新

let queue = [];
let isFlushing = false;

effect(
  () => {
    console.log(state.count);
  },
  {
    scheduler: () => {
      if (!isFlushing) {
        isFlushing = true;
        Promise.resolve().then(() => {
          queue.forEach((job) => job());
          queue = [];
          isFlushing = false;
        });
      }
      queue.push(() => {
        // 执行 effect
      });
    },
  }
);

作用: 合并同步的多次更新,在微任务中统一执行

场景 2:watch 的实现

function watch(source, cb) {
  let oldValue;

  const effect = new ReactiveEffect(getter);

  // 自定义调度器
  effect.scheduler = () => {
    const newValue = effect.run();
    cb(newValue, oldValue); // 执行回调
    oldValue = newValue;
  };

  oldValue = effect.run(); // 初始执行,收集依赖
}

工作流程:

  1. 第一次执行 effect.run() 收集依赖,获取 oldValue
  2. 数据变化时触发 scheduler
  3. scheduler 中获取 newValue 并调用用户回调
  4. 更新 oldValuenewValue

场景 3:条件执行

effect(
  () => {
    console.log("effect");
  },
  {
    scheduler: () => {
      if (shouldRun) {
        // 只在满足条件时执行
        effect.run();
      }
    },
  }
);

5. 调度器的执行时机

数据变化

触发 set

trigger

propagate

effect.notify()

effect.scheduler()  ← 这里可以自定义

effect.run()

6. Options 参数的处理

function effect(fn, options) {
  const e = new ReactiveEffect(fn);

  // 通过 Object.assign 将用户传入的 scheduler 覆盖默认的
  // 实例属性优先级高于原型属性
  Object.assign(e, options);

  e.run();

  const runner = e.run.bind(e);
  runner.effect = e;
  return runner;
}

关键点:

  • Object.assign 会将 options.scheduler 覆盖原型上的 scheduler 方法
  • 实例属性的优先级高于原型属性
  • 这是 JavaScript 属性查找机制的应用

核心概念总结

1. 响应式数据的本质

数据 (Dependency)
  ↕ 双向链表
Effect (Subscriber)
  • Dependency:响应式数据(ref/reactive 的属性)
  • Subscriber:副作用函数(effect)
  • Link:连接两者的链表节点

2. 依赖收集的时机

effect(() => {
  // 在 effect 执行期间
  const value = ref.value; // ← 此时收集依赖
});

条件:

  • 必须在 effect 执行期间
  • 必须访问响应式数据的 getter

3. 派发更新的时机

ref.value = newValue; // ← set 时触发

流程:

  1. 调用 setter
  2. 判断值是否变化(hasChanged
  3. 触发 trigger
  4. 遍历订阅者链表
  5. 调用每个 effect 的 notify 方法
  6. 执行 scheduler

4. 双向链表的优势

优势说明
快速添加通过 tail 指针 O(1) 添加节点
快速删除双向指针支持 O(1) 删除
节点复用对象池复用,减少 GC
灵活遍历支持双向遍历
自动清理轻松识别和清理过期依赖

5. 调度器的作用

应用场景作用
异步更新合并多次更新,减少执行次数
watch在数据变化时执行回调
computed延迟计算,按需更新
自定义逻辑完全控制 effect 执行时机

6. 重要的设计模式

对象池模式

let linkPool: Link; // 复用清理的节点

发布-订阅模式

// 订阅
link(dep, sub);
// 发布
propagate(dep.subs);

模板方法模式

notify() {
  this.scheduler();  // 父类定义流程,子类实现细节
}

学习心得

今日重点

  1. Ref 的实现:通过 getter/setter 拦截访问,实现依赖收集和派发更新
  2. Effect 的基础:理解 run 方法的执行流程,嵌套 effect 的处理
  3. 双向链表:Vue3 使用双向链表管理依赖关系,支持高效的增删操作
  4. 调度器机制:通过 scheduler 实现灵活的 effect 执行策略

关键收获

  • activeSub:当前正在执行的 effect,是依赖收集的核心
  • startTrack/endTrack:精确控制依赖收集的时机和清理逻辑
  • 节点复用:对象池模式减少 GC 压力
  • dirty 标记:避免重复触发,提升性能

待深入学习

  • Reactive 的实现(对象的响应式)
  • Computed 的惰性计算和缓存机制
  • Watch 的 deep、immediate、once 等选项
  • 调度器的批量更新策略
  • 异步组件的更新机制

代码示例

完整的 Effect 使用示例

import { ref, effect } from "@vue/reactivity";

// 1. 创建响应式数据
const count = ref(0);
const name = ref("Vue");

// 2. 创建 effect
const runner = effect(() => {
  console.log(`${name.value}: ${count.value}`);
});
// 输出: Vue: 0

// 3. 修改数据,自动触发 effect
count.value++;
// 输出: Vue: 1

name.value = "React";
// 输出: React: 1

// 4. 停止 effect
runner.effect.stop();

count.value++;
// 不再输出(effect 已停止)

自定义调度器示例

import { ref, effect } from "@vue/reactivity";

const count = ref(0);

// 使用自定义调度器
effect(
  () => {
    console.log("Effect run:", count.value);
  },
  {
    scheduler: () => {
      console.log("Scheduled!");
      // 可以在这里控制 effect 的执行
    },
  }
);

// 输出: Effect run: 0 (初次执行)

count.value++;
// 输出: Scheduled! (触发调度器,不是直接执行 effect)

学习总结: Vue3 的响应式系统通过精巧的双向链表设计,实现了高效的依赖追踪和更新派发。调度器机制赋予了开发者极大的灵活性,使得 Vue3 能够在保持简洁 API 的同时,支持复杂的响应式场景。

下一步计划: 深入学习 Reactive、Computed 的实现,以及 Vue3 的组件更新机制。