map和set是为键值映射与唯一集合问题设计的原生结构,非语法糖;误用(如map存只读配置、set内容去重)会导致性能下降或逻辑错误,须依语义选型并遵守响应式约束。

Map 和 Set 不是“更高级的对象或数组”,而是为特定问题设计的原生结构。用错场景、误读语义、忽视响应式限制,都会导致性能下降或逻辑错误。规避反模式的关键,在于理解它们的设计意图和边界。
别把 Map 当成只读配置对象
如果数据初始化后极少变动,仅高频读取(比如语言包、主题配置),用普通对象或字面量更轻量。Map 的哈希表管理开销在静态场景中是冗余的,且 Object 支持解构、JSON 序列化、for…in 枚举等更自然的操作。
- ✅ 正确:const i18n = { en: 'Hello', zh: '你好' };
- ❌ 反模式:const i18n = new Map([['en', 'Hello'], ['zh', '你好']]),后续只调用 .get()。
对象作 key 必须复用引用,别靠内容自动相等
Map 使用 SameValueZero 判等,{id: 1} 和 {id: 1} 是两个不同对象,.set() 和 .get() 永远不匹配。
- ✅ 正确:用同一对象引用,或改用唯一字符串 key(如 user.id);
- ✅ 若必须按内容判等,先 JSON.stringify(obj) 生成 key,但注意函数、undefined、Symbol 会丢失;
- ❌ 错误:每次 new 一个对象传给 .set(),再 new 一个一模一样的去 .get()。
Set 去重 ≠ 对象级去重
new Set([{a:1}, {a:1}]) 长度为 2,因为对象比较看引用而非内容。Set 只对基本类型(含 NaN)天然去重。
- ✅ 按字段去重:arr.map(x => x.id).filter((id, i, a) => a.indexOf(id) === i);或用 Map 记录首次索引;
- ✅ 大数组优化:const seen = new Set(); arr.filter(x => !seen.has(x.id) && seen.add(x.id));
- ❌ 直接 new Set(arr) 期望去掉内容相同的对象。
Pinia 中 Map/Set 必须用 ref 包裹
Vue 3 的 reactive() 不深层追踪 Map/Set 内部变化。直接赋值到 state 会导致 .set()、.add() 等操作不触发视图更新。
- ✅ 正确:permissions: ref(new Map()),读写都通过 .value;
- ✅ 模板中遍历:v-for="item in permissions.value";
- ❌ 错误:permissions: new Map(),然后 this.permissions.set(k, v) —— 界面不会响应;
- ⚠️ 注意 computed 返回值:若返回 store.permissions.value,就脱离了 ref 链路,后续操作不再响应。
批量操作优先构造函数,别循环 set/add
初始化万级以上数据时,new Map([[k1,v1],[k2,v2]]) 比 for 循环调用 .set() 快 2–3 倍(V8 实测)。同理,new Set(iterable) 优于逐个 add。
- ✅ 批量初始化:const map = new Map(dataArray.map(([k, v]) => [k, transform(v)]));
- ❌ 低效写法:const map = new Map(); dataArray.forEach(([k, v]) => map.set(k, transform(v)));
- ✅ Set 合并:const union = new Set(setA); for (const v of setB) union.add(v);
- ❌ 低效合并:new Set([...setA, ...setB]) —— 创建中间数组,GC 压力大。











