javascript中Map和Set是什么_与对象数组有何区别呢

Map 和 Set 是语义独立的原生集合类型:Set 适用于唯一性判断和存在性查询,用 Same-value-zero 算法正确处理 NaN;Map 支持任意键类型、无原型污染、按插入顺序遍历,适合缓存等场景。

MapSet 是 ES6 原生集合类型,不是对象或数组的语法糖——它们有独立语义、行为和性能特征,用错地方会埋坑。

什么时候该用 Set 而不是数组去重?

数组去重写 [...new Set(arr)] 看似简单,但背后是语义切换:你真正要的不是“一个去重后的数组”,而是“一个值是否存在”的快速判断。

  • 去重逻辑更准SetSame-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.stringifyMap 键存字符串靠谱得多

为什么 Map 比普通对象更适合作为缓存容器?

当你用 {} 存 DOM 元素、React 组件实例或配置项时,本质是在对抗 JavaScript 对象的设计限制。

  • 键类型自由:对象会把非字符串键隐式转成字符串,const obj = {}; obj[{} ] = 'a'; obj[{} ] = 'b'; 实际只存了一个键 '[object Object]';而 map.set({}, 'a'); map.set({}, 'b') 是两个不同键(因对象引用不同)
  • 无原型污染风险:对象若没用 Object.create(null) 创建,obj.hasOwnPropetyobj.toString 可能命中原型链上的同名方法;Map 完全不继承 Object.prototype
  • 顺序可控且可预测:遍历 Map 严格按插入顺序;对象属性遍历虽在现代引擎中大多有序,但规范不保证,且 for...in 还会遍历原型链上可枚举属性

MapSet 的常见误用陷阱

它们不是万能替代品,强行套用反而引入 bug。

  • 别用 Set 当数组用set[0]undefinedset.length 不存在,set.map 报错——它没有索引概念,只提供 add/has/delete。需要下标访问?还是用数组
  • 别拿对象当 Map 键还期望相等判断map.get({id: 1}) 永远返回 undefined,因为每次 {} 都是新引用。必须复用同一对象引用,或改WeakMap(仅限对象键 + 弱引用)
  • 别混淆 sizelengthmap.sizeset.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],就说明你已经偏离了它的原始意图。