【JavaScript】Set 和 Map 核心区别与实战用法(ES6 集合全解析)

Set 和 Map 都是 ES6 新增的有序集合类型 (区别于 Object 的无序/类型限制),但设计目标完全不同:Set 是「无重复值的集合」(仅存值,无键),核心用于去重、存在性判断Map 是「键值对集合」(键值映射),核心用于灵活的键值存储(替代 Object 解决键类型限制问题)。以下是全方位对比和实战指南。

一、核心定位(最本质区别)

集合类型 核心结构 核心特性 形象比喻
Set 「值的集合」 存储唯一、无序的值(无键,仅值),自动去重 像"无重复的数组",只关心值是否存在
Map 「键值对集合」 存储键值对(键任意类型、有序、唯一),无原型干扰 像"升级版 Object",键可存对象/数组

二、关键特性对比表

特性维度 Set(值集合) Map(键值对集合)
存储单元 仅存储「值」(value),无键 存储「键-值」对(key-value),键值一一映射
唯一性规则 「值」唯一(重复值添加无效),NaN 视为相等 「键」唯一(重复键会覆盖值),键判断规则同 Set
键/值类型限制 无键,值可以是任意类型(原始类型/引用类型) 键可以是任意类型(对象/数组/函数等),无类型转换;值也可任意类型
顺序 严格遵循「插入顺序」存储/迭代 严格遵循「插入顺序」存储/迭代
获取大小 内置 size 属性(直接返回值的数量) 内置 size 属性(直接返回键值对数量)
迭代方式 可直接 for...of/forEach,迭代值本身 可直接 for...of/forEach,迭代 [key, value] 数组
常用方法 add()/delete()/has()/clear()/values() set()/get()/delete()/has()/clear()/keys()/values()/entries()
是否可索引访问 无索引(不能用 set[0] 获取值) 无索引(不能用 map[0] 获取值,需用 get(key)
JSON 序列化 默认 JSON.stringify(set) 得到 [],需手动转换 默认 JSON.stringify(map) 得到 {},需手动转换

三、基础用法示例

1. Set 核心用法(去重、存在性判断)

javascript 复制代码
// 1. 创建 Set(可传入数组初始化)
const s = new Set([1, 2, 2, 3]); // 自动去重,s = Set(3) {1, 2, 3}
console.log(s.size); // 3

// 2. 新增值(重复值添加无效)
s.add(4); // Set(4) {1,2,3,4}
s.add(2); // 无变化(值已存在)

// 3. 判断值是否存在
console.log(s.has(2)); // true
console.log(s.has(5)); // false

// 4. 删除值
s.delete(3); // true(删除成功)
console.log(s); // Set(3) {1,2,4}

// 5. 迭代(三种方式)
// 方式1:for...of
for (const val of s) {
  console.log(val); // 1 → 2 → 4(插入顺序)
}
// 方式2:forEach
s.forEach(val => console.log(val));
// 方式3:转为数组(常用!利用 Set 去重)
const arr = [...s]; // [1,2,4]
const uniqueArr = [...new Set([1,2,2,3])]; // [1,2,3]

// 6. 清空
s.clear();
console.log(s.size); // 0

2. Map 核心用法(灵活键值存储)

javascript 复制代码
// 1. 创建 Map(可传入二维数组初始化)
const m = new Map([
  ['name', '张三'],
  [123, '数字键'],
  [{ id: 1 }, '对象键'] // 键可以是对象
]);
console.log(m.size); // 3

// 2. 新增/修改键值对(重复键覆盖值)
m.set('age', 18); // Map(4) {..., 'age' => 18}
m.set(123, '覆盖数字键'); // 键123的值被覆盖

// 3. 获取值(必须用 get(key),不能用 m.key)
console.log(m.get('name')); // 张三
console.log(m.get({ id: 1 })); // undefined(对象键是引用类型,需用同一个对象)
const objKey = { id: 1 };
m.set(objKey, '正确的对象键');
console.log(m.get(objKey)); // 正确的对象键

// 4. 判断键是否存在
console.log(m.has('age')); // true

// 5. 删除键值对
m.delete('age'); // true

// 6. 迭代(三种方式)
// 方式1:for...of(迭代 [key, value])
for (const [key, value] of m) {
  console.log(key, value); // name 张三 → 123 覆盖数字键 → {id:1} 正确的对象键
}
// 方式2:forEach(value 在前,key 在后)
m.forEach((value, key) => console.log(key, value));
// 方式3:迭代键/值
for (const key of m.keys()) console.log(key); // 所有键
for (const value of m.values()) console.log(value); // 所有值

// 7. 清空
m.clear();
console.log(m.size); // 0

四、核心差异实战示例

示例 1:Set 去重(最常用场景)

javascript 复制代码
// 数组去重(含 NaN,Set 视 NaN 为相等)
const arr = [1, 2, 2, NaN, NaN, { id: 1 }, { id: 1 }];
const uniqueArr = [...new Set(arr)];
console.log(uniqueArr); // [1,2,NaN, {id:1}, {id:1}](对象是引用类型,视为不同值)

// 字符串去重
const str = 'aabbcc';
const uniqueStr = [...new Set(str)].join(''); // 'abc'

示例 2:Map 解决 Object 键类型限制

javascript 复制代码
// Object 无法用对象做键(会转为字符串 "[object Object]")
const obj = {};
const key1 = { id: 1 };
const key2 = { id: 2 };
obj[key1] = '值1';
obj[key2] = '值2';
console.log(obj[key1]); // 值2(因为 key1/key2 都转为 "[object Object]",覆盖了)

// Map 可以用对象做键(引用类型键精准匹配)
const map = new Map();
map.set(key1, '值1');
map.set(key2, '值2');
console.log(map.get(key1)); // 值1(精准匹配)
console.log(map.get(key2)); // 值2

示例 3:Set 实现交集/并集/差集

javascript 复制代码
const s1 = new Set([1, 2, 3]);
const s2 = new Set([3, 4, 5]);

// 并集
const union = new Set([...s1, ...s2]); // Set(5) {1,2,3,4,5}

// 交集
const intersection = new Set([...s1].filter(x => s2.has(x))); // Set(1) {3}

// 差集(s1 有但 s2 没有)
const difference = new Set([...s1].filter(x => !s2.has(x))); // Set(2) {1,2}

五、适用场景选型(一句话记住)

场景需求 选 Set 选 Map
数组/字符串去重、判断值是否存在
存储唯一值集合(如标签、权限列表)
实现交集/并集/差集等集合运算
键值对存储(尤其是键为非字符串类型)
动态增删键值对、依赖插入顺序迭代
替代 Object 避免原型链冲突

典型实战场景

  1. Set 用例

    • 标签管理(如文章标签去重);
    • 权限校验(判断用户是否有某个权限);
    • 数组去重、集合运算。
  2. Map 用例

    • 缓存存储(如用 DOM 元素作为键存储对应数据);
    • 复杂键值对存储(如键为函数/数组);
    • 替代 Object 存储动态键值(避免 toString/hasOwnProperty 等原型键冲突)。

六、常见坑点与避坑

1. Set/Map 的引用类型判断

Set 的值、Map 的键若为引用类型(对象/数组),判断"相等"的依据是引用地址,而非内容:

javascript 复制代码
const s = new Set();
s.add({ id: 1 });
s.add({ id: 1 });
console.log(s.size); // 2(两个对象引用地址不同,视为不同值)

const obj = { id: 1 };
s.add(obj);
s.add(obj);
console.log(s.size); // 3(重复添加同一个对象,仅存一个)

2. NaN 的特殊性

Set/Map 视 NaN 为"相等"(即使 NaN !== NaN):

javascript 复制代码
const s = new Set([NaN, NaN]);
console.log(s.size); // 1(自动去重)

const m = new Map();
m.set(NaN, '值1');
m.set(NaN, '值2');
console.log(m.get(NaN)); // 值2(重复键覆盖)

3. 无法通过索引访问

Set/Map 都没有索引(如 set[0]/map[0]),需通过迭代或 get 方法获取值:

javascript 复制代码
const s = new Set([1,2,3]);
console.log(s[0]); // undefined(错误用法)
console.log([...s][0]); // 1(正确:转为数组后访问)

const m = new Map([['name', '张三']]);
console.log(m['name']); // undefined(错误用法)
console.log(m.get('name')); // 张三(正确:用 get 方法)

4. JSON 序列化问题

Set/Map 默认无法被 JSON.stringify 正确序列化,需手动转换:

javascript 复制代码
// Set 转为数组序列化
const s = new Set([1,2,3]);
const sJson = JSON.stringify([...s]); // "[1,2,3]"

// Map 转为二维数组序列化
const m = new Map([['name', '张三'], ['age', 18]]);
const mJson = JSON.stringify([...m]); // "[["name","张三"],["age",18]]"

// 反序列化
const mNew = new Map(JSON.parse(mJson)); // 恢复为 Map

七、总结

核心结论 Set Map
核心功能 去重、存唯一值、集合运算 灵活键值存储(替代 Object)
核心优势 自动去重、简洁的存在性判断 键任意类型、有序、无原型冲突
实战关键词 去重、标签、权限、集合运算 缓存、复杂键、动态键值对

简单来说:「存唯一值用 Set,存键值对用 Map」 。两者都是 ES6 为解决传统数组/对象缺陷设计的集合类型,Set 专注"值的唯一性",Map 专注"灵活的键值映射",配合数组的扩展运算符(...)可轻松实现转换和运算。

相关推荐
hoiii1872 小时前
MATLAB中主成分分析(PCA)与相关性分析的实现
前端·人工智能·matlab
小白|2 小时前
【OpenHarmony × Flutter】混合开发性能攻坚:如何将内存占用降低 40%?Flutter 引擎复用 + ArkTS 资源回收实战指南
开发语言·javascript·flutter
大波V52 小时前
用 nvm 彻底重装 Node 12.22.12(确保干净)
前端
和和和3 小时前
React Scheduler为何采用MessageChannel调度?
前端·javascript
Ric9703 小时前
Mac上Git不识别文件名大小写修改?一招搞定!
前端
momo061173 小时前
用一篇文章带你手写Vue中的reactive响应式
javascript·vue.js
他是龙5513 小时前
第29天:安全开发-JS应用&DOM树&加密编码库&断点调试&逆向分析&元素属性操作
开发语言·javascript·安全
和和和3 小时前
前端应该知道的浏览器知识
前端·javascript
狗哥哥3 小时前
前端基础数据中心:从混乱到统一的架构演进
前端·vue.js·架构