掌握Map与Set关键在于理解底层行为:Map键用SameValueZero判等,对象需序列化;Set仅浅层去重,NaN相等但对象不等;性能敏感时Object/Array更优;WeakMap/WeakSet适合弱引用;调试宜转数组打印。

掌握 Map 与 Set 的高级进阶技巧,关键不在记住所有 API,而在于理解它们的底层行为边界和真实业务约束。比如 Map 不是“万能字典”,Set 也不是“全自动去重器”——用错场景反而比 Object 或 Array 更难调试。
Map 的键不是“任意类型”,而是“任意值 + 严格判等逻辑”
很多人以为 Map 支持对象作 key 就万事大吉,但实际要注意:
- 对象作 key 时,判等用的是 SameValueZero(类似 ===,但 NaN === NaN),所以
{a:1}和{a:1}是两个不同 key,不会覆盖 - 想按内容判等?必须自己序列化:用
JSON.stringify(obj)当 key,但会丢掉函数、undefined、Symbol、循环引用 - Date、RegExp、Error 等每次 new 都是新实例,若误用作 key,会导致缓存击穿——建议用
date.getTime()或reg.toString()标准化 - DOM 元素或 class 实例可直接当 key,Map 内部不阻止 GC,适合做弱绑定缓存
Set 去重不是“智能识别”,而是“逐值比对”
Set 只做浅层唯一性判断,对嵌套结构完全无感:
-
new Set([{x:1}, {x:1}])长度是 2,因为两个对象内存地址不同 -
new Set([NaN, NaN])长度是 1,这是 SameValueZero 的特殊规则 -
new Set([null, undefined])长度是 2,null 和 undefined 被视为不同值 - 数组去重别只写
[...new Set(arr)]——它只对原始值有效;对象数组需配合map(item => JSON.stringify(item))+new Set()+JSON.parse手动处理
性能敏感场景下的 Map/Set 替代方案
不是所有“看起来该用 Map/Set”的地方都值得换:
- 键固定且数量少(如配置项:
{theme: 'dark', lang: 'zh'}),Object 语义更清晰、V8 优化更好 - 纯数字索引连续访问(如
arr[0],arr[1]),Array 比 Map 快 3–5 倍,尤其在百万级数据下 - 需要频繁取交集/并集?原生 Set 没有内置方法,得手写:
new Set([...setA].filter(x => setB.has(x))));复杂集合运算建议用lodash.intersection或转为 Array 处理 - 高频增删 + 遍历顺序无关?考虑
WeakMap(key 必须是对象,不阻止 GC)或WeakSet(仅存对象引用,适合标记已处理节点)
调试与排错的实用习惯
Map/Set 不像 Object 那样在控制台展开友好,容易掩盖问题:
- 打印前先转数组:
console.log([...myMap])或console.log([...mySet]),避免只看到Map(3) {} - 检查是否被意外清空:给 Map/Set 加一层包装类,重写
clear()方法并加 console.warn - 发现 size 不更新?确认没漏掉
map.set(key, value)的返回值被忽略——它不抛错,但 set 失败时静默失败(比如 key 是不可枚举属性) - 用
map.forEach((val, key) => {...})代替 for…of,避免因 key 类型导致的遍历异常(例如 key 是 Symbol 时 for…of 可能跳过)











