跳到主要内容

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

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

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

  • NaNNaN 视为相同
  • +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:MapObject 怎么选?

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

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

因为:

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

Q3:WeakMap 为什么不能遍历?

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


易错点/坑

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

速记要点(可背诵)

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