Map和Set用于高效处理键值映射与唯一值集合:Map支持任意类型键、插入顺序迭代;Set基于SameValueZero去重,适用于存在性检查与去重场景。
Map 和 Set 不是用来替代对象或数组的,而是解决它们在特定场景下无法高效、准确表达“键值映射”和“唯一值集合”的问题。
普通 Object 的键只能是字符串或 Symbol,且遍历时顺序不保证(实际引擎虽大多按插入顺序,但规范未规定);而 Map 允许任意类型作键(如对象、函数、NaN),且严格按插入顺序迭代。
常见错误现象:obj[{a:1}] = 'x' 实际变成 obj['[object Object]'] = 'x',键被强制转为字符串,多个不同对象会冲突。
实操建议:
Map 存以 DOM 元素、React 组件实例、类实例为键的元数据Map 的 delete() 比 delete obj[key] 更可靠(后者不触发原型链更新,且无法删除 undefined 值)map.forEach() 或 for (const [key, val] of map),而非 Object.keys() + for...in
const m = new Map();
const keyObj = { id: 1 };
m.set(keyObj, 'data for obj');
m.set(NaN, 'nan key'); // ✅ NaN 可作键,且能正确 get
console.log(m.get(keyObj)); // 'data for obj'
console.log(m.get(NaN)); // 'nan key'
Array 去重要写 [...new Set(arr)] 或用 filter() + indexOf(),低效且不能处理引用类型;Set 内部用哈希实现,add()/has() 平均时间复杂度 O(1),且自动去重。
使用场景:
fetch 请求共用同一组 ID,需去重后批量查)注意:Set 的去重基于 SameValueZero 算法(类似 ===,但 NaN 与自身相等),所以 new Set([NaN, NaN]) 长度为 1,但 {} 和 {} 仍是两个不同值,不会去重。
const seen = new Set();
const btn = document.getElementById('submit');
btn.addEventListener('click', () => {
if (seen.has(btn)) return;
seen.add(btn);
// 执行提交逻辑
});
Map 和 Set 比原生对象/数组略重——它们是集合类结构,有额外的内部哈希表和迭代器支持。如果只是静态配置项(如 { theme: 'dark', lang: 'zh' }),用 Object 更轻量、可压缩、V8 优化更好。
容易踩的坑:
Map 当成“高级对象”到处用,结果调试时发现 console.log(map) 不显示内容,得用 map.entries() 或展开符 [...map]
Set 能深比较对象,写 set.add({x:1}); set.has({x:1}) 返回 false(因为是不同引用)Map.prototype.keys() 得到的是迭代器,不是数组,不能直接调用 .map(),要先转 Array.from() 或展开JSON.stringify() 完全忽略 Map 和 Set,直接返回空对象 {} 或空数组 []。这意味着它们不适合直接作为跨环境数据载体(如 localStorage、fetch body、WebSocket 消息)。
若必须传输,需手动转换:
Map → 数组:用 Array.from(map.entries()),反向用 new Map(arr)
Set → 数组:用 [...set],反向用 new Set(arr)

toJSON 方法(但 JSON.stringify 不会自动调用它)这其实是设计使然:Map/Set 是运行时行为结构,不是数据格式。混淆这两者,是很多“为什么存不进 localStorage”的根源。