在 Vue 3 的响应式系统中,Symbol
、WeakMap
和 WeakSet
是构建其核心机制的关键工具。它们被广泛用于依赖追踪(Reactivity)、副作用管理(Effect) 和 避免内存泄漏 等场景。
下面我将结合 Vue 3 源码的设计思路,详细解释这些结构的使用方式和原理,并附上实际应用示例。
🧠 一、Vue 3 中的响应式系统概览
Vue 3 的响应式系统基于 Proxy + Reflect 实现,核心思想是:
- 对对象进行代理(reactive)
- 访问属性时收集依赖(track)
- 修改属性时触发更新(trigger)
而在这个过程中,Symbol
、WeakMap
和 WeakSet
分别承担了以下角色:
数据结构 | 用途 |
---|---|
Symbol | 创建唯一 key,作为 effect 的依赖标识符 |
WeakMap | 存储对象与依赖之间的映射关系 |
WeakSet | 避免循环引用导致无限递归 |
🔑 二、Symbol
的作用:唯一键名 & 元编程
✅ 用法示例:
const ITERATE_KEY = Symbol(__DEV__ ? 'iterate' : '');
📌 主要用途:
1. 作为特殊依赖键名
- 在遍历对象或数组时,用于标记“整个对象被访问”这一行为。
- 当某个 effect 使用了
for...in
或Object.keys()
时,会收集这个特殊的ITERATE_KEY
依赖。
2. 定义自定义行为符号
- 如
Symbol.iterator
、Symbol.toPrimitive
等,用于控制对象的行为。 - Vue 内部也会使用自定义 Symbol 来识别响应式代理对象。
3. 防止命名冲突
- 使用 Symbol 而不是字符串作为键名,确保不会与其他代码冲突。
🗃️ 三、WeakMap
的作用:存储依赖映射
✅ 用法示例:
const targetMap = new WeakMap();
📌 结构说明:
targetMap: {
target (原始对象) => depsMap,
depsMap: { key (属性名) => effects (依赖列表)
}
}
🎯 核心逻辑:
function track(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
dep.add(activeEffect); // 将当前 effect 加入依赖集合 }
🧩 示例说明:
const state = reactive({ count: 0 });
effect(() => { console.log(state.count); // track 触发 });
state.count++; // trigger 触发
targetMap
记录了{count: 0}
这个对象depsMap
记录了 count 属性对应的依赖(即上面的 effect)- 修改 count 时,会从
targetMap
找到对应依赖并执行
🚫 四、WeakSet
的作用:检测循环引用 & 避免重复处理
一、什么是循环引用?
const a = { name: 'A' }; const b = { name: 'B' }; a.friend = b; b.friend = a;
此时:
a.friend === b
b.friend === a
这就是典型的循环引用(circular reference)。
在响应式系统(如 Vue)、状态管理库、递归遍历中,这种结构非常常见,但如果不加以处理,会导致:
- 无限递归栈溢出
- 重复代理/处理对象
- 内存泄漏
✅ 用法示例:
function traverse(value, seen = new WeakSet()) {
if (typeof value !== 'object' || value === null)
return value;
if (seen.has(value)) return; // 遇到循环引用,停止遍历
seen.add(value);
for (let key in value) {
traverse(value[key], seen);
}
return value;
}
📌 主要用途:
1. 避免深度遍历时栈溢出
- 在
watch
或deep reactive
场景下,如果对象存在循环引用(如 A 引用 B,B 又引用 A),会导致无限递归。 - 使用
WeakSet
缓存已处理过的对象,可以有效避免死循环。
2. 不阻止垃圾回收
- 与普通
Set
不同,WeakSet
中的对象不会阻止垃圾回收器回收它。 - 如果一个对象只被
WeakSet
引用,当其他引用都被清除后,该对象仍然可以被回收。
🧩 五、综合实例:Vue 3 响应式系统的简化实现
const targetMap = new WeakMap();
let activeEffect;
function effect(fn) {
const effectFn = () => {
activeEffect = effectFn;
fn(); };
effectFn(); // 初次执行一次
}
function track(target, key) {
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) { depsMap.set(key, (dep = new Set())); }
dep.add(activeEffect); }
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return; const effects = depsMap.get(key); if (effects) { effects.forEach(effect => effect()); } }
function reactive(target) { return new Proxy(target, { get(target, key, receiver) { track(target, key);
return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
if (oldValue !== value) { trigger(target, key); } return result; } }); }
🧪 使用示例:
const state = reactive({ count: 0 });
effect(() => { console.log('Count:', state.count); });
state.count++; // 输出 Count: 1
🧠 总结:Vue 3 中 Symbol / WeakMap / WeakSet 的职责
数据结构 | 用途 | 是否参与响应式系统 |
---|---|---|
Symbol | 定义唯一键名、迭代标识、元编程 | ✅ |
WeakMap | 存储对象 → 依赖的映射关系 | ✅ |
WeakSet | 防止循环引用、临时标记对象 | ✅ |
🛠️ 实际建议
如果你正在开发大型 Vue 项目或封装自己的响应式库,建议:
- 使用
WeakMap
替代全局变量缓存依赖 - 使用
WeakSet
处理深层嵌套对象或树形结构时的循环引用问题 - 使用
Symbol
定义唯一的内部 key,避免命名冲突 - 合理使用
Proxy + Reflect
构建响应式系统
六、对比总结:Map
vs WeakMap
,Set
vs WeakSet
特性 | Map | WeakMap | Set | WeakSet |
---|---|---|---|---|
是否可存储原始值 | ✅ | ❌(只能是对象) | ✅ | ❌ |
是否可遍历 | ✅ | ❌ | ✅ | ❌ |
是否强引用 | ✅ | ❌(弱引用) | ✅ | ❌ |
是否适合长期存储 | ✅ | ❌(适合临时引用) | ✅ | ❌ |
是否防止内存泄漏 | ❌ | ✅ | ❌ | ✅ |
是否支持循环引用检测 | ❌ | ✅ | ❌ | ✅ |
📌 七、适用场景建议
场景 | 推荐数据结构 |
---|---|
缓存对象与其代理 | WeakMap |
标记已处理对象 | WeakSet |
存储元信息(如组件状态) | WeakMap |
避免深度遍历时栈溢出 | WeakSet |
状态持久化、日志记录 | Map / Set |
✅ 总结
数据结构 | 是否解决循环引用 | 是否防内存泄漏 | 是否推荐用于响应式系统 |
---|---|---|---|
Map | ❌ | ❌ | 否 |
WeakMap | ✅ | ✅ | ✅ 推荐 |
Set | ❌ | ❌ | 否 |
WeakSet | ✅ | ✅ | ✅ 推荐 |
如果你正在开发一个类似 Vue 的响应式框架、状态管理模块或需要深度遍历对象的系统,合理使用 WeakMap
和 WeakSet
可以显著提升性能并避免内存问题。