vue3 存储依赖的数据结构
WeakMap 和 Map 详解
1. Map
定义:
Map 是一种键值对的集合,允许使用任意类型的值(对象、原始类型)作为键,保持插入顺序,支持遍历和大小查询。
核心特性:
- 键的多样性:键可以是任意数据类型(包括对象、函数等),通过严格相等比较(
===)来匹配。 - 顺序性:插入顺序被保留,迭代时按插入顺序返回键值对。
- 内存管理:若键是对象,即使该对象在其他地方被销毁,Map 仍保留其引用,可能导致内存泄漏。
- 方法和属性:
set(key, value):添加键值对。get(key):获取对应值。has(key)、delete(key)、clear()。size:返回元素数量。keys()、values()、entries():返回迭代器。
使用场景:
- 需要复杂键(如对象关联数据)。
- 需要维护插入顺序(如历史记录)。
- 需要频繁遍历或查询大小。
示例:
const map = new Map();
const keyObj = { id: 1 };
map.set(keyObj, 'Value for object');
console.log(map.get(keyObj)); // 'Value for object'
// 迭代示例
for (const [key, value] of map) {
console.log(`${key}: ${value}`);
}
2. WeakMap
定义:
WeakMap 是一种特殊的键值对集合,键必须是对象,值可以是任意类型。键采用弱引用,不会阻止垃圾回收(GC)。
核心特性:
- 弱引用键:若键对象没有其他引用,即便在 WeakMap 中存在,也会被 GC 回收。
- 不可枚举性:不支持遍历(如
keys()、values())和size查询。 - 自动清理:键对象被回收后,对应的键值对会被自动移除。
方法和属性:
set(key, value):仅接受对象作为键。get(key)、has(key)、delete(key)。- 注意:无
clear()、size或迭代方法。
使用场景:
- 存储对象的私有数据或元数据(避免内存泄漏)。
- 缓存临时对象关联的信息(如 DOM 元素附加数据)。
示例:
const weakMap = new WeakMap();
let obj = { id: 1 };
weakMap.set(obj, 'Private data');
console.log(weakMap.get(obj)); // 'Private data'
// 当 obj 被设为 null,且无其他引用时,GC 会自动回收该键值对。
obj = null;
3. 对比 Map 和 WeakMap
| 特性 | Map | WeakMap |
|---|---|---|
| 键类型 | 任意类型 | 仅对象 |
| 垃圾回收影响 | 阻止键对象的回收(强引用) | 不阻止(弱引用) |
| 可枚举性 | 支持遍历和 size |
不可枚举 |
| 方法完整性 | 完整的方法(如 clear()) |
仅有 set/get/has/delete |
| 典型使用场景 | 通用键值存储、需遍历的数据 | 对象关联元数据、内存敏感场景 |
4. 常见问题
Q1: 何时优先选用 WeakMap?
当需要存储与对象生命周期绑定的私有数据时(例如类实例的隐藏属性),WeakMap 能自动清理数据,避免内存泄漏。
Q2: 为什么 WeakMap 不支持遍历?
弱引用的特性导致键可能随时被回收,无法保证迭代时所有键都存在,因此设计上不支持遍历。
Q3: WeakMap 如何用于缓存?
若缓存键是对象且希望对象销毁时缓存失效,WeakMap 可避免手动清理缓存。例如保存 DOM 元素的计算结果,元素移除后缓存自动删除。
在 Vue3 的响应式系统中,依赖收集是其核心机制之一,通过 WeakMap + Map + Set 组合的层级结构来管理依赖关系(称为 targetMap)。下面从源码角度详细解析其设计。
1. 依赖收集的存储结构 (targetMap)
核心结构关系
// 核心类型定义(简化版)
type Target = object; // 被代理的原始对象
type Dep = Set<ReactiveEffect>; // 依赖集合(存放副作用函数)
type Key = string | symbol; // 对象的 key(属性名)
type DepsMap = Map<Key, Dep>; // 每个对象的键对应一个依赖集合
// 全局存储核心结构
const targetMap = new WeakMap<Target, DepsMap>();
-
targetMap:
一个 WeakMap,用于存储所有响应式对象与其依赖(DepsMap)的关联。
键(Key): 代理的原始对象(Target)。
值(Value): 该对象的DepsMap。 -
DepsMap:
一个 普通 Map,用于管理对象每个属性(Key)对应的依赖集合。
键(Key): 字符串或 Symbol(对象属性名)。
值(Value): 该属性关联的依赖集合Dep(存放副作用函数)。 -
Dep:
一个 Set 集合,存储与当前属性相关的所有副作用函数(ReactiveEffect)。
2. 源码流程解析
关键位置源码
- 源码文件:
packages/reactivity/src/effect.ts,packages/reactivity/src/reactive.ts
(1) 依赖收集 (track())
当访问响应式属性时,track() 函数会触发依赖收集:
export function track(target: object, key: Key) {
if (!activeEffect) return; // 当前无活动的 effect,直接返回
// 1. 查找或创建 target 对应的 depsMap
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
// 2. 查找或创建 key 对应的 dep
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
// 3. 将当前活动的 effect(副作用函数)添加到 dep 中
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
activeEffect.deps.push(dep); // 反向记录 effect 的依赖集合
}
}
流程解释:
- 通过
targetMap查找原始对象对应的depsMap。 - 在
depsMap中查找该属性(Key)对应的依赖集合dep。 - 将当前活动的副作用函数(
activeEffect)添加到dep中,并建立双向关联。
(2) 依赖触发 (trigger())
当修改响应式属性时,trigger() 函数触发副作用函数的执行:
export function trigger(
target: object,
key: Key,
type: TriggerOpTypes // 操作类型(SET/ADD/DELETE等)
) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
// 1. 获取 key 对应的依赖集合
const dep = depsMap.get(key);
// 2. 可能触发的其他依赖(如数组的 length 变更,或对象的新增/删除操作)
const effects: ReactiveEffect[] = [];
if (dep) effects.push(...dep);
// 3. 遍历并执行所有相关的 effect
for (const effect of effects) {
if (effect.options.scheduler) {
effect.options.scheduler(effect); // 触发调度器(如异步更新)
} else {
effect(); // 直接执行
}
}
}
执行过程:
- 根据原始对象和属性名查找
dep。 - 收集所有关联的
effect。 - 通过
scheduler进行调度(例如 Vue3 的 异步批处理更新)。
3. 数据结构设计优势
-
WeakMap 管理原始对象到依赖的映射:
- 避免内存泄漏,当原始对象不再被引用时,
targetMap中的条目会自动被 GC 回收。
- 避免内存泄漏,当原始对象不再被引用时,
-
Map 记录属性到依赖集合的关系:
- 快速通过属性名(Key)查找到对应的
dep,无需遍历。
- 快速通过属性名(Key)查找到对应的
-
Set 存储副作用函数(
ReactiveEffect):- 自动去重,避免同一个副作用函数被重复收集。
4. 与副作用函数 (ReactiveEffect) 的关联
每个副作用函数(ReactiveEffect)会被动态绑定到当前活动的 activeEffect,并通过 track() 收集到对应的 dep 中。
ReactiveEffect 结构:
class ReactiveEffect {
deps: Dep[] = []; // 记录该 effect 被哪些 dep 收集(用于清除依赖)
constructor(
public fn: () => void, // 副作用函数本体
public options?: ReactiveEffectOptions
) {}
run() {
activeEffect = this; // 标记为当前活动的 effect
return this.fn();
}
}
关键操作:
- 执行
effect.run()时,activeEffect指向当前 effect。 - 副作用函数执行过程中访问响应式数据,触发
track()将activeEffect收集到dep中。 - 当响应式数据变化时,通过
dep找到所有关联的effect并重新执行。
5. 示例工作流
假设有以下场景:
const obj = reactive({ count: 0 });
effect(() => {
console.log(obj.count); // 访问属性,触发 track
});
obj.count++; // 修改属性,触发 trigger
代码的存储结构变化:
reactive(obj)被 Proxy 包装,访问obj.count时会触发track(target, 'count')。- 创建结构:
targetMap: WeakMap{ { count: 0 } => Map{ 'count' => Set[ effect ] } } obj.count++修改时触发trigger(target, 'count'),从Set中找到effect并执行。
总结
Vue3 的依赖收集通过 WeakMap → Map → Set 的层级结构高效管理对象、属性和副作用函数的关系:
- WeakMap:响应式对象 → 依赖映射,弱引用避免内存泄漏。
- Map:对象属性 → 依赖集合,快速通过 Key 查找。
- Set:存储唯一副作用函数,支持高效的增删查操作。
这一设计在性能与内存管理之间取得了平衡,是 Vue3 响应式系统的核心基础。

浙公网安备 33010602011771号