讲解
Map 是真正的「键值对」结构,补上了普通对象的短板:键可以是任意类型(对象、函数、NaN 都行,对象的键只能是字符串或 Symbol);有 size 属性直接拿大小;键保持插入顺序;为频繁增删做过优化。API 很直觉:set 设值、get 取值(不存在返回 undefined)、has 判断、delete 删除、clear 清空,遍历用 for...of 得到 [键, 值] 对。选择标准:纯数据且键是字符串、需要 JSON 序列化时用对象;键类型多样、频繁增删、需要大小时用 Map。
Set 是不重复值的集合:add 添加(重复添加无效)、has 判断存在、delete 删除、size 大小。它最经典的用途是数组去重:[...new Set(arr)] 一行搞定。Set 判断重复用 SameValueZero 语义:基本类型按值,对象按引用,NaN 等于 NaN(与 === 不同)。两个 Set 做交并差集可以配合 filter 手写,ES2025 标准还加入了 union/intersection/difference 等方法。
两者都是可迭代的,都能用展开转数组。要提醒的是 Map 和 Set 都不能被 JSON.stringify 直接序列化(Map 变 {},Set 也变 {}),需要序列化时先转成数组。WeakMap/WeakSet 是弱引用版本,键被回收后条目自动消失,适合给对象附加元数据而不阻止回收。
示例
// Map:任意类型的键
const visits = new Map();
const pageA = { path: '/home' };
visits.set(pageA, 3);
visits.set('about', 10);
visits.set(42, 1);
console.log('对象作键:', visits.get(pageA));
console.log('大小:', visits.size);
// Map 遍历保持插入序
for (const [key, value] of visits) {
console.log('条目:', typeof key === 'object' ? key.path : key, '=>', value);
}
// 用 Map 做计数器
const words = ['a', 'b', 'a', 'c', 'b', 'a'];
const counter = new Map();
for (const w of words) {
counter.set(w, (counter.get(w) ?? 0) + 1);
}
console.log('词频:', Object.fromEntries(counter));
// Set:去重与成员判断
const tags = new Set(['js', 'web', 'js']);
tags.add('css');
console.log('去重:', [...tags]);
console.log('包含 web:', tags.has('web'), '大小:', tags.size);
// 数组去重一行流
const nums = [1, 2, 2, 3, 3, 3];
console.log('数组去重:', [...new Set(nums)]);
// Set 求交集
const setB = new Set([2, 3, 4]);
console.log('交集:', [...new Set(nums)].filter((x) => setB.has(x)));
常见坑
- 用对象当 Map 的键时期望按内容匹配:map.get({ a: 1 }) 取不到 set({ a: 1 }, v) 存的值——对象键按引用比较,每次字面量都是新对象。
- 直接 JSON.stringify(Map/Set):结果是 {},数据全丢。先 [...map] 转数组或 Object.fromEntries 转对象。
- Set 去重对象数组无效:[...new Set([{a:1},{a:1}])] 两个都保留,因为引用不同。对象去重要按键用 Map 或 filter。
- 遍历时增删:Map/Set 遍历时删除未访问元素是安全的,但添加的元素会被遍历到,容易写出不结束的循环。
小结
Map 支持任意键、有 size、保序;Set 存唯一值、去重一行流。两者可迭代、不能直接 JSON 序列化。下一节看更底层的 Symbol 与迭代协议。