Map/Set不可替代对象/数组,因键类型任意、严格区分值类型、O(1)查增删、保持插入顺序、无原型污染、正确处理NaN;对象键强制转字符串、无序、性能差。

Map 为什么不能用普通对象 {} 替代
因为对象的键会强制转成字符串,而 Map 允许任意类型作键——包括 DOM 元素、函数、null、undefined,甚至两个不同但结构相同的对象也能区分。
常见错误现象:obj[divEl] 实际变成 obj["[object HTMLDivElement]"],多个节点全挤进同一个键,缓存彻底失效。
- 需要缓存按钮展开状态?
const cache = new Map(); cache.set(buttonEl, { expanded: true })✅;用对象就崩 - 键是数字
42还是字符串"42"很关键?Map 严格区分;对象里obj[42] === obj["42"]❌ - 要快速知道存了多少条?
map.size是 O(1);Object.keys(obj).length每次都新建数组,O(n)
Set 去重和查存在,为什么比 Array.includes() 快得多
Set 底层是哈希表,set.has(x) 平均时间复杂度是 O(1);数组的 includes() 或 indexOf() 是线性扫描,O(n)。
性能影响在数据量大时特别明显:10 万用户 ID 中查一个是否存在,set.has(id) 几乎无感;arr.includes(id) 可能卡顿半秒以上。
立即学习“Java免费学习笔记(深入)”;
- 数组去重别再写
arr.filter((v, i) => arr.indexOf(v) === i),一行搞定:[new Set(arr)] - 防重复提交?
if (!pendingRequests.has(url)) { pendingRequests.add(url); /* 发请求 */ }✅;用数组每次都要遍历 - 注意:对象去重无效——
new Set([{a:1}, {a:1}])会存两个,因为每次{}都是新引用;要用同一对象才能命中
哪些场景必须用 Map/Set,硬套对象或数组会出错
不是“更高级”,而是“非它不可”。对象和数组在这几类需求下语义模糊、行为不可靠、甚至直接崩溃。
- 缓存 DOM 状态:
nodeCache.set(div, { dirty: true })→ 用对象就全变成"[object HTMLDivElement]",覆盖掉 - 实时在线用户列表:
onlineUsers.add(userId)+onlineUsers.delete(userId)+onlineUsers.has(userId),高频增删查,顺序不重要但性能敏感 → Set 最稳 - 权限白名单:
const allowed = new Set(['read', 'edit']); if (allowed.has(action)) { ... }→ 比['read','edit'].includes(action)更快、更清晰、不依赖数组方法兼容性 - LRU 缓存实现:
map.keys().next().value能直接拿到最早插入项 → 对象没有插入顺序保证,无法可靠实现
容易被忽略的关键细节
Map 和 Set 不是语法糖,它们有独立内存模型和行为规范。很多坑不是写错了,而是没意识到它们和对象/数组的底层差异。
-
Set不支持索引访问:mySet[0]是undefined;要取第 n 个值,得先转数组:[...mySet][n] -
Map没有原型污染风险:map.get('constructor')安全;但obj.constructor可能被覆盖或继承干扰 - 对象键对
NaN不友好(obj[NaN] = 1; obj[NaN]是undefined),而Set和Map用 SameValueZero 算法,NaN能正确识别为相同值











