跳到主要内容

Set 和 Map:为什么它们不是“对象和数组的语法糖”?该怎么选?

面试速答(30 秒版 TL;DR)​

  • Set 是“值的集合”,核心特点是 元素唯一;Map 是“键值对集合”,核心特点是 键可以是任意值,不局限于字符串/Symbol。
  • 两者都保持 插入顺序,并且都天然可迭代。
  • Set 最常见用途是去重、成员判断;Map 最常见用途是缓存、索引、对象键映射。
  • 面试最高频是:Object vs Map 怎么选、Array 去重为什么常用 Set、WeakMap/WeakSet 解决什么问题。

心智模型:它们是“集合结构”,不是普通对象的替身​

一句话理解:

  • Set 关心“有没有这个值”
  • Map 关心“这个键对应什么值”

它们解决的是:

  • 更稳定的键集合管理
  • 更明确的插入顺序
  • 更自然的存在性判断

一、Set 的常用 API​

const set = new Set([1, 2, 2, 3]);
console.log(set); // Set(3) {1, 2, 3}
API作用
new Set(iterable?)创建 Set
add(value)添加值
has(value)判断是否存在
delete(value)删除值
clear()清空
size元素个数
keys() / values()遍历值
entries()遍历 [value, value]
forEach(fn)遍历

为什么 entries() 是 [value, value]​

因为 Set 没有独立的 key 概念,为了和 Map/迭代协议接口保持一致,entries() 才返回成对结构。


二、Set 的典型场景​

1) 数组去重​

const unique = [...new Set([1, 2, 2, 3])];
// [1, 2, 3]

2) 快速成员判断​

const whiteList = new Set(["admin", "editor"]);
whiteList.has("admin"); // true

3) 集合运算​

const a = new Set([1, 2, 3]);
const b = new Set([3, 4, 5]);

const intersection = new Set([...a].filter((x) => b.has(x)));
const union = new Set([...a, ...b]);
const diff = new Set([...a].filter((x) => !b.has(x)));

三、Map 的常用 API​

const map = new Map();
map.set("name", "Werry");
map.set({ id: 1 }, "object-key");
API作用
new Map(iterable?)创建 Map
set(key, value)设值
get(key)取值
has(key)判断键是否存在
delete(key)删除键
clear()清空
size键值对个数
keys()遍历键
values()遍历值
entries()遍历 [key, value]
forEach(fn)遍历

四、Map 为什么比普通对象更像“真正的哈希表”​

对比项ObjectMap
键类型字符串 / Symbol任意值
默认原型属性有没有键污染问题
是否天然可迭代否是
size 获取Object.keys(obj).lengthmap.size
插入顺序有自己的属性枚举规则明确按插入顺序迭代

面试口径:

  • 如果你要表达“字典对象”,且键可能不是字符串,优先 Map
  • 如果你要表达“固定结构的普通配置对象”,用 Object 更自然

五、Set / Map 的相等性规则​

它们判断键或值是否相同,通常基于 SameValueZero:

  • NaN 和 NaN 视为相同
  • +0 和 -0 视为相同
  • 对象比较的仍然是引用地址
const set = new Set([NaN, NaN]);
set.size; // 1

const map = new Map();
map.set({}, 1);
map.get({}); // undefined,不是同一个引用

六、为什么对象做键时 Map 特别重要​

const user = { id: 1 };
const cache = new Map();

cache.set(user, { posts: [] });
cache.get(user); // { posts: [] }

如果换成普通对象:

  • 键会被转成字符串
  • 两个不同对象很难安全地区分

七、WeakMap / WeakSet 解决什么问题​

WeakMap​

  • 键必须是对象
  • 键是弱引用
  • 对象外部没人再引用时,可以被 GC 回收

典型用途:

  • 给 DOM 节点、组件实例挂“附属元数据”
  • 不想因为缓存把对象强行留在内存里

WeakSet​

  • 成员必须是对象
  • 常用于“标记某对象处理过”

注意:

  • WeakMap / WeakSet 不可枚举
  • 因为它们的内容可能随 GC 随时变化

八、典型使用场景​

1) Set 去重与权限集合​

  • 去重标签
  • 判断某 feature flag 是否启用

2) Map 做缓存与索引​

  • 请求缓存
  • 节点到数据的映射
  • LRU 的基础结构之一

典型题 & 标准答法​

Q1:Map 和 Object 怎么选?​

  • 键是任意值、需要频繁增删查、需要保留插入顺序时,选 Map
  • 表达固定字段结构的普通数据时,选 Object

Q2:为什么数组去重常写成 [...new Set(arr)]?​

因为:

  • 表达直接
  • 利用了 Set 的唯一性
  • 代码简洁,可读性好

Q3:WeakMap 为什么不能遍历?​

因为它的键是弱引用,内容可能随着 GC 动态消失;如果还能稳定枚举,就和“弱引用”语义冲突了。


易错点/坑​

  • Map 用对象做键时,比较的是引用,不是结构相等。
  • Set 去重对象时,只能去掉“同一引用”的重复,不能做深度去重。
  • 把所有字典都改成 Map 并不一定更好,固定结构对象用 Object 更自然。

速记要点(可背诵)​

  • Set:唯一值集合,常用于去重和成员判断。
  • Map:任意键的键值对集合,常用于缓存和索引。
  • Object 更像结构体,Map 更像字典。
  • WeakMap / WeakSet 的核心价值是“不阻止垃圾回收”。