开发小透明 & 面试大明星:Map 和 Set 的双面人生
开发小透明 & 面试大明星:Map 和 Set 的双面人生。 在 JavaScript 的江湖中,有两位常被误解的角色:* **开发中:小透明,不出场也没人想念它们*** **面试时:大明星,不问它都觉得不专业**
开发小透明 & 面试大明星:Map 和 Set 的双面人生
在 JavaScript 的江湖中,有两位常被误解的角色:
- 开发中:小透明,不出场也没人想念它们
- 面试时:大明星,不问它都觉得不专业
它们就是 —— Map 和 Set。
如果你觉得这两个 ES6 新数据结构“偶尔能用,用了也没啥感觉”,那么这篇文章会刷新你的认知: 它们不只是“高级玩意儿”,而是现代前端工程化的基石数据结构之一。
本文我会从 故事 → 行为 → 实战 → 底层 → 最佳实践 五个维度带你把它们吃透。
一、为什么开发里它们像“小透明”?
因为过去我们一直在用以下组合:
- Array + 去重
- Object + 查找
- Object + KV 存储
- Array + indexOf 判断重复
- JSON 结构来做 Map(笑)
这些组合当然能用,但在 性能、语义、结构设计、扩展性 上, 已经远落后现代工程的需求。
ES6 给了我们真正的集合结构: Map = 高性能字典 Set = 保证唯一性的集合
但很多人没意识到“换个数据结构”能带来如此大的提升,于是:
你不知道它好,只是因为你长期将就。
二、Map:从“字典工具人”到“专业 Key-Value 管家”
在 Object 的世界里,key 只能是 字符串或 Symbol,而 Map 说:
key?只要你敢给,我就敢存。
对象、函数、NaN、DOM 节点、数字、正则…… 只要是引用,都能作为 key。
更贴近真实业务的数据关系
const users = new Map();
const userObj = { id: 1 };
users.set(userObj, { name: "Alice", age: 22 });
console.log(users.get(userObj));
// { name: 'Alice', age: 22
在 Object 中,这根本做不到,也不合理(只能把对象转成字符串强行塞进去)。
## Map 的核心能力:不是“能存”,而是“存得专业”
### 1. 插入顺序有保证
```js
const m = new Map();
m.set('z', 1).set('a', 2).set('m', 3);
for (const k of m.keys()) console.log(k);
// z, a, mObject 在这方面完全不可靠。
2. 高性能:频繁增删的王者
在 10 万次操作中,Map 通常比 Object 快一倍以上。
console.time('Map');
for (let i = 0; i < 100000; i++) {
map.set(i, i);
map.delete(i);
}
console.timeEnd('Map');3. NaN === NaN?在 Map 里 Yes。
map.set(NaN, "OK");
console.log(map.get(NaN)); // OKJS 自身都做不到的事情 Map 做到了,这很专业。
三、Set:唯一性的天生保障者(去重神器)
Set 是程序员的“保安”:
你们都能进来?不行,我要保证唯一。
const s = new Set([1,2,2,3]);
console.log([...s]); // [1,2,3]再也不用写:
arr.filter((v, i) => arr.indexOf(v) === i)这种“过时检查方式”。
Set 的必会三板斧(面试高频)
1. 去重
[...new Set([1, 2, 2, 3])]2. 集合运算:并集 / 交集 / 差集
const A = new Set([1,2,3]);
const B = new Set([3,4,5]);
// 并集
new Set([...A, ...B]);
// 交集
new Set([...A].filter(x => B.has(x)));
// 差集
new Set([...A].filter(x => !B.has(x)));3. 判断是否存在(极快)
set.has(value)比 Array.includes() 快得多。
四、开发为什么需要 Map / Set?
它们解决的是 数据结构设计的痛点。
痛点 1:Object 当 Map 用的天然缺陷
- key 不自由
- 无序
- 容易和原型链冲突
- 无 size
- 性能弱
痛点 2:Array 去重、查找都是 O(n)
Set/Map 基本都是 O(1)
痛点 3:有些业务天然是集合关系,而不是数组关系
五、真实业务场景:不懂 Map/Set 都会写更复杂的代码
以下全是真实项目中的“高频场景”。
场景 1:Session 管理系统(Map + Set)
class SessionManager {
constructor() {
this.sessions = new Map(); // userId -> sessionData
this.activeUsers = new Set(); // 在线用户
}
login(userId, sessionData) {
this.sessions.set(userId, { ...sessionData, lastActive: Date.now() });
this.activeUsers.add(userId);
}
logout(userId) {
this.sessions.delete(userId);
this.activeUsers.delete(userId);
}
getOnlineUsers() {
return [...this.activeUsers].map(id => this.sessions.get(id));
}
}用 Object 和 Array 实现这个? 更慢、更丑、更复杂。
场景 2:LRU 缓存(典型 Map 模式)
class LRUCache {
constructor(limit = 100) {
this.limit = limit;
this.cache = new Map();
}
get(key) {
if (!this.cache.has(key)) return;
const val = this.cache.get(key);
this.cache.delete(key);
this.cache.set(key, val);
return val;
}
set(key, val) {
if (this.cache.has(key)) this.cache.delete(key);
else if (this.cache.size >= this.limit) {
const oldest = this.cache.keys().next().value;
this.cache.delete(oldest);
}
this.cache.set(key, val);
}
}LRU 的底层 = 有序 Map + O(1) 操作 Object 根本做不到。
场景 3:权限系统(Set 作为权限集)
class Permission {
constructor() {
this.userRoles = new Map();
this.rolePermissions = new Map();
}
assignRole(uid, role) {
if (!this.userRoles.has(uid)) this.userRoles.set(uid, new Set());
this.userRoles.get(uid).add(role);
}
can(uid, perm) {
const roles = this.userRoles.get(uid) || new Set();
for (const role of roles) {
const perms = this.rolePermissions.get(role) || new Set();
if (perms.has(perm)) return true;
}
return false;
}
}这就是 Map + Set 的典型搭配: 一次存储,多维查询,复杂关系建模。
六、真正的高手,还会用 WeakMap / WeakSet
因为它们能自动 GC 回收数据。 适用于:
- 存储类/对象的私有数据
- 存储 DOM 元素相关的信息
- 避免内存泄漏
const privateData = new WeakMap();
class Person {
constructor(name) {
privateData.set(this, { name });
}
get name() {
return privateData.get(this).name;
}
}对象销毁后私有数据自动清理,不需要手动管理。
七、总结:Map / Set 为什么面试大明星?
因为它们解决了 JS 最根本的结构问题:
| 维度 | Object/Array | Map/Set |
|---|---|---|
| Key 类型 | 有限制 | 任意类型 |
| 唯一性 | 手动维护 | Set 自动保证 |
| 顺序性 | 不稳定 | 保序 |
| 查找性能 | O(n) | O(1) |
| 增删效率 | 低 | 高 |
| 表意性 | 弱 | 强 |
| 适合建模吗 | 一般 | 完美 |
面试官真正想听的不是 API,而是:
“你能否在大型系统里,用对数据结构。”
懂 Map / Set 的前端,是可以把数据结构写进架构的人。
最后:一句话送给你
Map 和 Set 不是为了让代码更‘炫’,而是为了让代码更‘对’。 用合适的数据结构,是工程师从初级走向高级的关键分水岭。
周温
全栈开发工程师,专注于前端技术栈和物联网应用开发。拥有丰富的 React、Next.js、Nest.js 项目经验,擅长构建高性能、可扩展的 Web 应用。热爱技术分享,致力于将复杂的技术问题转化为易懂的实践指南。