一、前言:为什么需要 Set 和 Map?
在 JavaScript 中,数组(Array)和对象(Object)是最常用的数据结构,但它们在特定场景下存在明显短板:
- 数组查找元素需遍历,时间复杂度 O (n),且去重需额外处理;
- 对象的键只能是字符串 /.Symbol,无法直接使用引用类型作为键,且无法快速获取键值对数量。
ES6 引入的Set(集合)和Map(映射),正是为解决这些痛点而生 —— 它们提供了更高效的查找、插入、删除操作,以及更灵活的键类型支持,成为复杂业务场景的优选数据结构。
二、Set:不重复元素的有序集合
2.1 核心特性
- 元素唯一:自动去重,重复添加的元素会被忽略;
- 有序存储:按插入顺序保存元素(ES6 规范),遍历顺序与插入顺序一致;
- 支持多种类型:可存储字符串、数字、布尔值、引用类型(对象、数组等);
- 无键名只有值:本质是 “值 - 值” 映射,可理解为 “只有键的数组”。
2.2 常用 API(含示例)
// 1. 初始化
const set = new Set([1, 2, 2, 'a', true]); // 自动去重:Set(4) {1, 2, 'a', true}
// 2. 增删改查
set.add(3); // 添加元素:Set(5) {1, 2, 'a', true, 3}
set.delete('a'); // 删除元素:返回true(成功)/false(失败)
set.has(2); // 查找元素:返回true
set.size; // 获取长度:4
set.clear(); // 清空集合:Set(0) {}
// 3. 遍历方式
const set2 = new Set(['x', 'y', 'z']);
// 方式1:for...of遍历
for (const val of set2) {
console.log(val); // x, y, z
}
// 方式2:forEach遍历
set2.forEach((val, index) => {
console.log(val); // x, y, z(index与val相同)
});
// 方式3:转换为数组
const arr = [...set2]; // ['x', 'y', 'z']
2.3 实战场景
- 数组去重:const uniqueArr = [...new Set(arr)];(简洁高效,时间复杂度 O (n));
- 数据筛选:存储不重复的 ID、标签等,如const userIds = new Set(['101', '102', '101']);;
- 快速判断存在性:比数组indexOf更高效(Set 查找为 O (1),数组为 O (n))。
三、Map:键值对的灵活映射
3.1 核心特性
- 键类型无限制:可使用字符串、数字、对象、数组、Symbol 等作为键;
- 有序存储:按插入顺序保存键值对,遍历顺序与插入顺序一致;
- 键唯一:重复设置同一键会覆盖旧值;
- 可迭代:支持直接遍历,无需像对象那样先获取键数组。
3.2 常用 API(含示例)
// 1. 初始化
const map = new Map([
['name', '张三'],
[18, '年龄'],
[{ id: 1 }, '用户信息'] // 对象作为键
]);
// 2. 增删改查
map.set('gender', '男'); // 添加键值对:Map(4) {['name','张三'], [18,'年龄'], [{id:1},'用户信息'], ['gender','男']}
map.get(18); // 获取值:'年龄'
map.has('name'); // 查找键:true
map.delete({ id: 1 }); // 注意:对象键是引用类型,需用同一引用才能删除
map.size; // 获取长度:3
map.clear(); // 清空映射
// 3. 遍历方式
const map2 = new Map([['a', 1], ['b', 2]]);
// 方式1:遍历键值对(最常用)
for (const [key, val] of map2) {
console.log(key, val); // a 1, b 2
}
// 方式2:遍历键/值
for (const key of map2.keys()) { /* 遍历键 */ }
for (const val of map2.values()) { /* 遍历值 */ }
// 方式3:forEach遍历
map2.forEach((val, key) => {
console.log(key, val); // a 1, b 2
});
3.3 关键注意点
- 引用类型作为键:对象、数组等引用类型作为键时,只有引用完全一致才视为同一键:
const obj1 = { id: 1 };
const obj2 = { id: 1 };
const map = new Map([[obj1, 'test']]);
map.get(obj2); // undefined(obj1与obj2是不同引用)
- 与对象的区别:
特性 | Map | Object |
键类型 | 任意类型 | 字符串 / Symbol |
遍历顺序 | 插入顺序 | 无序(ES6 前) |
长度获取 | size 属性直接获取 | 需手动计算(Object.keys (obj).length) |
迭代性 | 直接迭代 | 需先获取键数组 |
四、Set 与 Map 的核心区别与选型建议
4.1 核心区别
维度 | Set | Map |
存储结构 | 单值集合(值 - 值) | 键值对映射(键 - 值) |
核心用途 | 存储不重复元素 | 键值对关联查询 |
常用操作 | 去重、判断存在性 | 键值映射、数据关联 |
遍历返回值 | 直接返回元素 | 返回 [key, value] 数组 |
4.2 选型建议
- 若需存储不重复的数据(如 ID、标签、去重数组),优先用Set;
- 若需键值对映射(如配置表、缓存数据、字典),优先用Map;
- 若键是引用类型(如对象),只能用Map;
- 若只需简单的字符串键映射,且无需有序性,可考虑普通对象(但 Map 更灵活)。
五、性能优化与注意事项
- 性能对比:
- 插入 / 删除 / 查找操作:Set > Map > 数组 > 普通对象(数据量越大,优势越明显);
- 遍历速度:Map > Set > 数组 > 普通对象(Map/Set 直接迭代,无需额外处理)。
- 注意事项:
- Set的forEach回调中,index 参数与 value 相同(设计特性,无需纠结);
- Map的键是引用类型时,需保存同一引用才能修改 / 删除(避免隐式错误);
- 避免用for...in遍历 Map/Set(不支持,需用for...of或forEach);
- 序列化问题:JSON.stringify无法直接序列化 Map/Set,需手动转换为数组:
// Map序列化
const map = new Map([['a', 1]]);
const json = JSON.stringify([...map]); // "[[\"a\",1]]"
// Set序列化
const set = new Set([1, 2]);
const json = JSON.stringify([...set]); // "[1,2]"
六、总结
Set 和 Map 作为 ES6 的核心数据结构,解决了数组和普通对象的诸多痛点:Set 提供高效的不重复集合管理,Map 提供灵活的键值对映射。掌握它们的特性、API 和适用场景,能让你的代码更简洁、高效、易维护 —— 在数据去重、缓存管理、字典映射等场景中,它们往往是比数组和对象更优的选择。
建议在实际开发中多尝试用 Set/Map 替代传统数据结构,感受其带来的开发效率提升~