Map 和 Set 是语义独立的原生集合类型:Set 适用于唯一性判断和存在性查询,用 Same-value-zero 算法正确处理 NaN;Map 支持任意键类型、无原型污染、按插入顺序遍历,适合缓存等场景。
Map 和 Set 是 ES6 原生集合类型,不是对象或数组的语法糖——它们有独立语义、行为和性能特征,用错地方会埋坑。
Set 而不是数组去重?数组去重写 [...new Set(arr)] 看似简单,但背后是语义切换:你真正要的不是“一个去重后的数组”,而是“一个值是否存在”的快速判断。
Set 用 Same-value-zero 算法,NaN === NaN 返回 false,但 Set 能正确识别两个 NaN 是重复值;数组用 filter((v, i) => arr.indexOf(v) === i) 则完全失效set.has(x) 平均 O(1);数组用 arr.includes(x) 是 O(n),尤其在大列表里查 DOM 元素或用户 ID 时差距明显const seen = new Set(); if (!seen.has(obj)) { seen.add(obj); /* 处理 */ } —— 这比 JSON.stringify 或 Map 键存字符串靠谱得多Map 比普通对象更适合作为缓存容器?当你用 {} 存 DOM 元素、React 组件实例或配置项时,本质是在对抗 JavaScript 对象的设计限制。
const obj = {}; obj[{} ] = 'a'; obj[{} ] = 'b'; 实际只存了一个键 '[object Object]';而 map.set({}, 'a'); map.set({}, 'b') 是两个不同键(因对象引用不同)Object.create(null) 创建,obj.hasOwnPropety 或 obj.toString 可能命中原型链上的同名方法;Map 完全不继承 Object.prototype
Map 严格按插入顺序;对象属性遍历虽在现代引擎中大多有序,但规范不保证,且 for...in 还会遍历原型
链上可枚举属性Map 和 Set 的常见误用陷阱它们不是万能替代品,强行套用反而引入 bug。
Set 当数组用:set[0] 是 undefined,set.length 不存在,set.map 报错——它没有索引概念,只提供 add/has/delete。需要下标访问?还是用数组Map 键还期望相等判断:map.get({id: 1}) 永远返回 undefined,因为每次 {} 都是新引用。必须复用同一对象引用,或改用 WeakMap(仅限对象键 + 弱引用)size 和 length:map.size 和 set.size 是属性,不是方法;obj.length 在普通对象上永远是 undefined,而 Object.keys(obj).length 无法统计不可枚举属性或 Symbol 键const cache = new Map();
const button = document.querySelector('button');
// ✅ 正确:用 DOM 元素作键,安全、高效、可回收
cache.set(button, { clicked: true, timestamp: Date.now() });
// ❌ 错误:用对象字面量作键,每次都是新引用,查不到
cache.get({ id: 'btn-1' }); // undefined
// ✅ 正确:先存再取,用同一个引用
const key = { id: 'btn-1' };
cache.set(key, 'data');
cache.get(key); // 'data'
关键点在于:别把它当成“高级对象”或“带去重的数组”来用。Map 是为任意键 + 顺序 + 快速查存设计的;Set 是为唯一性 + 存在性判断设计的。一旦开始写 map[key] 或 set[i],就说明你已经偏离了它的原始意图。