Set 和 Map 的区别

SetMap 是 ES6 新增的有序集合类型(区别于普通对象 / 数组),核心定位不同:Set 是「值的集合」(无重复值),Map 是「键值对的集合」(键可任意类型),二者在数据结构、使用场景、核心方法上差异显著,以下是全面对比:

一、核心定位与本质区别

特性 Set Map
本质 「值的集合」(无序?不,ES6 后 Set 是有序的,按插入顺序迭代),元素唯一 「键值对的集合」(有序,按插入顺序迭代),键唯一、值可重复
核心作用 存储不重复的单一值(去重、成员检测) 存储键值对(键可为任意类型,替代普通对象)
数据结构 单值结构:[value1, value2, ...] 键值对结构:[[key1, value1], [key2, value2], ...]
索引 / 访问方式 无索引,无法通过下标访问,只能遍历 / 检测存在性 可通过 key 访问 valuemap.get(key)
重复判定规则 基于「值的严格相等(===)」,NaN 视为相等(弥补 === 缺陷) 键的重复判定同 Set(严格相等),值无限制

二、核心方法与属性对比

1. 基础属性

属性 Set Map 说明
size ✅(元素个数) ✅(键值对个数) 替代普通对象的 Object.keys(obj).length
keys() ✅(返回值的迭代器) ✅(返回键的迭代器) Set 的 keys() 等价于 values()
values() ✅(返回值的迭代器) ✅(返回值的迭代器) Map 核心取值方法
entries() ✅(返回 [value, value] 迭代器) ✅(返回 [key, value] 迭代器) Set 为了和 Map 统一接口,entries 每一项是 [值, 值]

2. 核心操作方法

操作场景 Set 方法 Map 方法
添加元素 add(value)(返回 Set 自身,可链式) set(key, value)(返回 Map 自身,可链式)
获取元素 无(只能 has() 检测,或遍历) get(key)(无则返回 undefined
检测存在性 has(value)(返回布尔值) has(key)(检测键是否存在)
删除元素 delete(value)(返回布尔值,是否删除成功) delete(key)(返回布尔值)
清空集合 clear()(无返回值) clear()(无返回值)
遍历 forEach((value, value, set) => {}) forEach((value, key, map) => {})

三、代码示例:核心用法差异

1. Set 示例(去重、成员检测)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 1. 初始化
const s = new Set([1, 2, 2, 3]); // 自动去重
console.log(s.size); // 3
console.log(s); // Set(3) {1, 2, 3}

// 2. 核心操作
s.add(4); // 链式:s.add(4).add(5)
console.log(s.has(2)); // true
s.delete(3); // true
s.forEach(v => console.log(v)); // 1,2,4

// 3. 常用场景:数组去重
const arr = [1, 2, 2, 3];
const uniqueArr = [...new Set(arr)]; // [1,2,3]

// 4. 注意:NaN 视为相等
s.add(NaN);
s.add(NaN);
console.log(s.size); // 4(仅存一个 NaN)

2. Map 示例(任意类型键、键值对存储)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 1. 初始化
const m = new Map([
["name", "张三"],
[123, "数字键"],
[true, "布尔键"],
[{ id: 1 }, "对象键"] // 键可以是对象(普通对象不行)
]);
console.log(m.size); // 4

// 2. 核心操作
m.set("age", 18); // 链式:m.set("gender", "男").set("city", "北京")
console.log(m.get("name")); // 张三
console.log(m.has(true)); // true
m.delete(123); // true
m.forEach((v, k) => console.log(`${k}: ${v}`)); // name: 张三, true: 布尔键...

// 3. 注意:键的严格相等
const objKey = { id: 1 };
m.set(objKey, "新值");
console.log(m.get({ id: 1 })); // undefined(两个不同的对象引用)
console.log(m.get(objKey)); // 新值

四、关键细节补充

1. 与普通对象 / 数组的对比(为什么用 Set/Map)

  • Set vs 数组:数组允许重复值,Set 自动去重;Set 的 has() 检测存在性(O (1))比数组 includes()(O (n))效率更高;

  • Map vs 普通对象

    • 普通对象的键只能是字符串 / 符号(Symbol),Map 的键可以是任意类型(对象、数字、布尔等);
    • Map 有 size 属性,普通对象需手动计算;
    • Map 按插入顺序迭代,普通对象(ES6 前)无序;
    • Map 可直接遍历,普通对象需先转数组(Object.keys/values/entries)。

2. 性能差异

  • Set:添加 / 删除 / 检测存在性(add/delete/has)的时间复杂度为 O (1),优于数组的 O (n);
  • Map:键的增删改查(set/get/delete/has)均为 O (1),普通对象的键操作也是 O (1),但 Map 对大量键值对的迭代效率更高。

五、适用场景对比

Set 适用场景

  1. 数组去重(最常用):[...new Set(arr)]
  2. 成员存在性检测(如权限判断、标签去重):s.has(tag)
  3. 存储不重复的单一值集合(如用户 ID 列表、标签列表);
  4. 避免重复添加(如向集合中添加元素前,用 has() 检测)。

Map 适用场景

  1. 键为非字符串类型(如用对象 / 数字作为键):普通对象无法实现;
  2. 需要频繁增删键值对(Map 的 deletedelete obj.key 更高效);
  3. 需要有序的键值对集合(按插入顺序迭代);
  4. 键值对数量动态变化,需快速获取长度map.sizeObject.keys(obj).length 更高效);
  5. 替代普通对象存储复杂键值对(如缓存数据、状态映射)。

六、核心总结

维度 Set Map
核心结构 单值集合,值唯一 键值对集合,键唯一、值可重复
访问方式 无直接访问,仅检测 / 遍历 通过键访问值(get(key)
核心优势 自动去重、高效的存在性检测 键类型无限制、有序、高效的增删查
适用场景 去重、成员检测、存储单一不重复值 任意类型键的键值对存储、动态键值对集合