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。
立即学习“Java免费学习笔记(深入)”;
-
别用
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],就说明你已经偏离了它的原始意图。











