JavaScript 中 Map 和 Set 的使用教程与最佳实践
·
在 ES6(ES2015)中,JavaScript 引入了两个非常实用的内置数据结构:Map 和 Set。它们弥补了传统对象(Object)和数组(Array)在某些场景下的不足,提供了更高效、更语义化、更安全的数据存储与操作方式。
本文将全面讲解 Map 与 Set 的核心特性、使用方法、对比优势,并结合真实开发场景给出最佳实践建议。
一、为什么需要 Map 和 Set?
❌ 传统方式的痛点
1. 用普通对象 {} 做键值对存储:
- 键只能是字符串或 Symbol,不能是对象、数字等;
- 原型链污染风险:
obj.toString可能是继承来的,而非你设置的值; - 无法直接获取长度(需
Object.keys(obj).length); - 无迭代顺序保证(虽然现代 JS 已规范,但语义不清)。
2. 用数组 [] 去重或集合操作:
- 去重要手动实现(如
filter + indexOf),效率低; - 判断元素是否存在需遍历(
O(n)),而 Set 是O(1)。
✅
Map和Set正是为解决这些问题而生!
二、Map:真正的键值对集合
🔑 核心特性
- 键可以是任意类型:字符串、数字、对象、函数、甚至
NaN; - 插入顺序保留:遍历时按插入顺序返回;
- 内置 size 属性:
map.size直接获取元素数量; - 无原型污染:不会继承 Object.prototype 的属性。
🧩 基本用法
// 创建 Map
const map = new Map();
// 添加键值对
map.set('name', 'Alice');
map.set(42, 'answer');
map.set({ id: 1 }, 'user object'); // 键是对象!
// 获取值
console.log(map.get('name')); // 'Alice'
console.log(map.get(42)); // 'answer'
// 检查是否存在
console.log(map.has('name')); // true
// 删除
map.delete('name');
// 清空
map.clear();
// 获取大小
console.log(map.size); // 动态更新
🔁 遍历 Map
const map = new Map([
['a', 1],
['b', 2]
]);
// 遍历键值对(推荐)
for (const [key, value] of map) {
console.log(key, value);
}
// 其他迭代器
map.keys(); // 迭代所有键
map.values(); // 迭代所有值
map.entries(); // 默认迭代器,等同于 for...of
🔄 与 Object 对比
| 特性 | Object | Map |
|---|---|---|
| 键类型 | 仅字符串/Symbol | 任意类型 |
| 插入顺序 | ES2015+ 保证(但语义弱) | 明确保证 |
| size | 需 Object.keys().length | .size 直接获取 |
| 序列化 | JSON.stringify 支持 | 不支持(需自定义) |
| 性能 | 小数据快 | 大量数据增删更快 |
💡 建议:
- 当键是运行时动态生成或非字符串 → 用
Map;- 当数据结构是固定字段的记录(如用户信息)→ 用
Object。
三、Set:唯一的值集合
🔑 核心特性
- 自动去重:添加重复值会被忽略;
- 值可以是任意类型;
- 插入顺序保留;
O(1)时间复杂度判断存在性(比数组indexOf快得多)。
🧩 基本用法
// 创建 Set
const set = new Set();
// 添加值
set.add(1);
set.add('hello');
set.add({}); // 对象引用不同即视为不同
set.add(1); // 重复,被忽略
console.log(set.size); // 3
// 检查是否存在
console.log(set.has(1)); // true
// 删除
set.delete('hello');
// 清空
set.clear();
🔁 遍历 Set
const set = new Set([1, 2, 3]);
for (const item of set) {
console.log(item);
}
// 转为数组
const arr = [...set];
// 或
const arr2 = Array.from(set);
✂️ 实用技巧:数组去重
// 最简洁的去重方式(ES6+)
const unique = [...new Set([1, 2, 2, 3, 1])]; // [1, 2, 3]
// 支持对象去重?不行!因为 {a:1} !== {a:1}
// 如需对象去重,需自定义逻辑(如基于 id)
🆚 与 Array 对比
| 场景 | 推荐 |
|---|---|
| 需要频繁判断元素是否存在 | Set(has() 是 O(1)) |
| 需要保持插入顺序且去重 | Set |
需要索引访问(如 arr[0]) | Array |
| 存储对象并按 ID 去重 | Map(ID 作 key) |
四、高级用法与实战场景
✅ 场景1:缓存计算结果(Memoization)
const cache = new Map();
function expensiveCalc(n) {
if (cache.has(n)) return cache.get(n);
const result = /* 复杂计算 */;
cache.set(n, result);
return result;
}
✅ 比用对象更安全(避免
__proto__等键冲突)。
✅ 场景2:统计元素出现次数
const count = (arr) =>
arr.reduce((map, val) => {
map.set(val, (map.get(val) || 0) + 1);
return map;
}, new Map());
const freq = count(['a', 'b', 'a']); // Map { 'a' => 2, 'b' => 1 }
✅ 场景3:实现双向映射(BiMap)
class BiMap {
constructor() {
this.forward = new Map();
this.reverse = new Map();
}
set(key, value) {
this.forward.set(key, value);
this.reverse.set(value, key);
}
getKey(value) {
return this.reverse.get(value);
}
getValue(key) {
return this.forward.get(key);
}
}
✅ 场景4:去重对象数组(基于某个字段)
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 1, name: 'Alice Updated' }
];
// 基于 id 去重
const uniqueUsers = [
...new Map(users.map(user => [user.id, user])).values()
];
// 结果:保留第一个 id=1 的用户
🔥 技巧:
Map的键会自动去重,values()提取唯一对象。
五、最佳实践与注意事项
✅ 推荐使用场景
| 数据结构 | 适用场景 |
|---|---|
Map | - 键不是字符串 - 需要频繁增删键值对 - 键值对数量动态变化大 |
Set | - 需要去重 - 需要快速判断元素是否存在 - 存储唯一标识(如已选标签 ID) |
⚠️ 注意事项
-
对象作为键时,比较的是引用:
const map = new Map(); map.set({}, 'value1'); map.set({}, 'value2'); // 不同对象,两个条目! -
NaN在 Set/Map 中被视为相等:new Set([NaN, NaN]).size; // 1 ✅ 符合 IEEE 754 -
Map和Set不能直接 JSON 序列化:JSON.stringify(new Map()); // "{}" // 需自定义序列化: JSON.stringify([...map]); // 转为数组再序列化 -
WeakMap / WeakSet 更适合 DOM 引用或私有数据(避免内存泄漏),但本文不展开。
六、性能对比(简要)
- 小数据量(< 100):Object / Array 可能略快(引擎优化);
- 大数据量或频繁操作:
Map/Set性能更稳定,尤其是has()、delete()操作; - 内存占用:
Map/Set略高,但通常可接受。
📊 实测建议:关键路径上用
console.time对比,不要过早优化。
七、总结
| 特性 | Map | Set |
|---|---|---|
| 用途 | 键值对存储 | 唯一值集合 |
| 键/值类型 | 任意 | 任意 |
| 去重 | 键自动去重 | 值自动去重 |
| 判断存在 | .has(key) | .has(value) |
| 获取数量 | .size | .size |
| 遍历顺序 | 插入顺序 | 插入顺序 |
✅ 记住一句话:
- 需要“键 → 值”映射,且键不是字符串? → 用
Map- 需要“唯一值”集合? → 用
Set
掌握 Map 和 Set,能让你的代码更高效、安全、语义清晰,是现代 JavaScript 开发的必备技能!
更多推荐



所有评论(0)