Map / Set / WeakMap / WeakSet,一次给你讲透

面试中经常被问:你了解 WeakMap / WeakSet 吗?

实际开发中也常有人困惑:我什么时候该用 Map,而不是 Object?Weak 到底弱在哪?

这篇文章,我会从最熟悉的 Object 讲起,一步步到 Map、Set,最后深入 WeakMap 和 WeakSet。

一、从 Object 说起:我们最熟悉,也最容易踩坑

在 JavaScript 里,对象几乎无处不在:

js 复制代码
const person = { name: "张三" };
console.log(person.name); // 张三

for (const key in person) {
  console.log(key, person[key]); // name 张三
}

delete person.name;
console.log(person.name); // undefined

我们对 Object 已经非常熟悉了:

  • 可以通过 . 或 [] 访问属性
  • 可以用 for...in 遍历
  • 可以用 delete 删除属性

但 Object 天生就不是为了「做集合」设计的。

一个真实的小坑

假设你想做一个"字典",key 可以是任意值:

js 复制代码
const obj = {};
const a = {};
const b = {};

obj[a] = 'A';
obj[b] = 'B';

console.log(obj); // { "[object Object]": "B" }

你以为是两个 key,实际上:

  • 对象的 key 只能是字符串或 Symbol
  • 非字符串会被隐式转换成字符串

这也是 Map 诞生的原因之一。

二、Map:为"键值对集合"而生

可以把 Map 理解成:一个"升级版 Object",但专门用来存键值对

1. 创建和添加数据

js 复制代码
const map = new Map();
map.set('name', '张三');
map.set('phone', 'iPhone');

特点很明确:

  • set(key, value) 添加数据
  • key 可以是任意类型(对象、函数、基本类型)
  • 同一个 key 只会存在一份
js 复制代码
map.set('phone', 'Galaxy'); // 覆盖

2. 读取、判断、长度

js 复制代码
map.get('phone'); // Galaxy
map.has('phone'); // true
map.size; // 2

3. Map 是可迭代的

这是它和 Object 的一个重要区别

js 复制代码
for (const [key, value] of map) {
  console.log(key, value);
}
// name 张三
// phone Galaxy

要仅获取键或值,还有一些方法可供使用

js 复制代码
map.keys() // MapIterator {'name', 'phone'}
map.values() // MapIterator {'张三', 'Galaxy'}
map.entries() // MapIterator {'name' => '张三', 'phone' => 'Galaxy'}
map.forEach(item => {})

甚至可以直接展开:

js 复制代码
[...map]; // [['name', '张三'],['phone', 'Galaxy']]

4. 删除与清空

js 复制代码
map.delete('phone'); // true
// 清空所有
map.clear(); // Map(0) {}

三、WeakMap:真正让人迷惑的地方来了

WeakMap起源于Map,因此它们彼此非常相似。但是,WeakMap 具有很大的不同

弱?弱在哪里?

核心一句话

WeakMap 的 key 是"弱引用",不会阻止垃圾回收

1. key 只能是对象

js 复制代码
const wm = new WeakMap();
wm.set({}, 'data'); // ✅
wm.set('a', 1);    // ❌ TypeError

原因很简单:

  • WeakMap 的设计目标:绑定对象的"附加信息"
  • 如果 key 是基本类型,就谈不上 GC

2. 为什么不能遍历?

想象这样一个场景:

js 复制代码
let user = { name: 'John' };
const wm = new WeakMap();
wm.set(user, 'meta');

user = null; // 断开引用

这时候:

  • 垃圾回收 随时可能发生
  • WeakMap 中的数据 可能突然消失

如果还能遍历,那结果就是不稳定的

所以 ES 规范直接规定:

  • ❌ 不可遍历
  • ❌ 没有 size
  • ✅ 只有 get / set / has / delete

3. WeakMap 的真实使用场景

一个非常经典的例子:

js 复制代码
const wm = new WeakMap();

function process(obj) {
  if (!wm.has(obj)) {
    wm.set(obj, { count: 0 });
  }
  wm.get(obj).count++;
}
  • 不污染原对象
  • 对象销毁后,数据自动释放
  • 不会内存泄漏

这也是 WeakMap 最大的价值。

四、Set:只关心"值是否存在"

如果说 Map 是 Object 的替代品,

那 Set 更像是"升级版数组" 。

1. 成员唯一

js 复制代码
const set = new Set();
set.add(1);
set.add(1);
set.add(NaN);
set.add(NaN);

结果:

js 复制代码
Set { 1, NaN }

规则总结:

  • 基本类型:值相同 → 只存一个
  • 引用类型:地址相同 → 只存一个
  • NaN 在 Set 中被认为是"相等的"

2. 可遍历

js 复制代码
for (const val of set) {}
set.forEach(val => {})

3. 实战:数组去重、交并差集

js 复制代码
[...new Set([1,1,2,3])]; // [1,2,3]

Set 在这类场景下,简洁又高效。

五、WeakSet:存在,但很低调

WeakSet 和 WeakMap 的理念是一样的:

  • 成员是对象
  • 成员是弱引用
  • 不可遍历
js 复制代码
let obj = { a: 1 };
const ws = new WeakSet();
ws.add(obj);

obj = null; // 被 GC

你永远不知道它什么时候"少了一个成员",

所以:

WeakSet 适合做"对象存在性标记",而不是数据容器


六、一张表彻底记住它们

类型 key/value 限制 是否可遍历 GC 影响
Object key 只能是字符串 可遍历 强引用
Map key 任意 ✅ 强引用
WeakMap key 只能是对象 ❌ 弱引用
Set value 任意 ✅ 强引用
WeakSet value 只能是对象 ❌ 弱引用

如何一句话答 WeakMap / WeakSet区别

WeakMap / WeakSet 的核心在于"弱引用 + 不可遍历",

它们不会阻止垃圾回收,适合存放与对象生命周期绑定的附加数据,用来避免内存泄漏。

如果这篇文章对你有帮助,欢迎点赞、收藏,

相关推荐
不一样的少年_16 分钟前
别用前端思维写后端:一张 5MB 图片,为什么能撑爆内存?
前端·后端·图片资源
沐言人生18 分钟前
太吊了!这 2 款网盘工具,让我省 4000 大洋!
前端
鬼手点金22 分钟前
opencode-隐私优先配置
服务器·前端·javascript·bug·openclaw
且从容.23 分钟前
JS篇-----
java·javascript·jvm
涛涛ing25 分钟前
4200万月活、85万行代码,Lovable用6个月把生产应用从Next.js迁到了TanStack Start
前端
悠仁さん28 分钟前
【C++】auto
java·前端·c++
吠品1 小时前
箭头函数为什么不能 new
开发语言·javascript·原型模式
liangshanbo12151 小时前
面试题:React.memo 和 useMemo 的区别
前端·react.js·前端框架
苹果二1 小时前
使用Vue的指南
前端·javascript·vue.js
害人终害己1 小时前
‘vue-cli-service‘ 不是内部或外部命令,也不是可运行的程序或批处理文件。
前端·javascript·vue.js