在 JavaScript 中,Map 和 WeakMap 都是用于存储键值对的集合数据结构。它们在许多方面有相似之处,但也有关键的区别,特别是在如何处理键和垃圾回收方面。下面将详细介绍它们的特点、用法及其区别。
1. Map:常规的键值对集合
Map 是一种有序的键值对集合,允许任何类型的键,并且会按照插入顺序进行迭代。Map 在 ES6 中引入,它是用来替代普通对象(Object)的,与普通的 JavaScript 对象不同,Map 的键可以是任何数据类型,用于需要更复杂操作的数据存储场景。
1.1 Map 的基本特点
- 键的类型:可以是任何类型(包括对象、函数、基本类型(如字符串、数字等))。
- 插入顺序:
Map会按插入顺序保存键值对,保证迭代顺序。 - 性能:对于频繁插入和删除的操作,
Map比普通对象更高效,因为它的插入、查找、删除操作时间复杂度通常为 O(1)。 - 没有原型链问题:
Map是一个纯粹的数据结构,没有继承自Object.prototype,因此不存在继承链上的属性(如toString、hasOwnProperty等)干扰。 - 大小:
Map有一个size属性,可以返回Map中键值对的数量。 - 迭代:
Map本身是可迭代的,支持forEach循环和其他迭代方法(如for...of)。
1.2 Map 的常用方法
set(key, value):添加或更新一个键值对。
let map = new Map();
map.set('name', 'Alice');
map.set(1, 'one');
get(key):根据键获取值。
console.log(map.get('name')); // 'Alice'
console.log(map.get(1)); // 'one'
has(key):检查是否存在某个键。
console.log(map.has('name')); // true
console.log(map.has('age')); // false
delete(key):删除指定键的键值对。
map.delete('name');
console.log(map.has('name')); // false
clear():清空所有键值对。
map.clear();
console.log(map.size); // 0
size:返回Map中键值对的数量。
console.log(map.size); // 2
forEach():对每个键值对执行回调函数。
map.forEach((value, key) => {
console.log(key, value);
});
keys()、values()和entries():分别返回键、值和键值对的迭代器。
for (let key of map.keys()) {
console.log(key);
}
for (let value of map.values()) {
console.log(value);
}
for (let [key, value] of map.entries()) {
console.log(key, value);
}
1.3 Map 的优势
- 更灵活的键类型:与普通对象只能使用字符串作为键不同,
Map可以使用任意类型(包括对象和函数)作为键。 - 保持插入顺序:
Map会根据插入顺序返回键值对,这是普通对象无法保证的。 - 避免原型链干扰:
Map不会受到继承链中其他属性的影响(如hasOwnProperty、toString等)。 - 高效的性能:
Map在频繁的插入、删除和查找操作中通常更高效。
2. WeakMap:带有垃圾回收机制的键值对集合
WeakMap 是 Map 的一种变体,专门用于存储对象的引用,并且具有“弱引用”机制。WeakMap 中的键必须是对象,而其值可以是任意类型,WeakMap 不会阻止其键对象被垃圾回收。
2.1 WeakMap 的基本特点
- 键必须是对象:
WeakMap只能使用对象作为键,不能使用原始值(如数字、字符串等)。这是因为WeakMap会使用“弱引用”来存储键,而弱引用是指当键不再有其他引用时,它可以被垃圾回收机制自动清除。 - 垃圾回收:
WeakMap的键是弱引用的,这意味着如果WeakMap中的某个键没有其他引用时,它会被垃圾回收机制自动清除,不会阻止该对象被回收。与此相对,普通的Map会保持对键的强引用,直到手动删除。 - 不支持迭代:
WeakMap不支持像Map那样进行迭代(没有forEach()、keys()、values()和entries()方法),因为WeakMap的键值对会在没有其他引用时被自动清除,无法保证稳定的迭代顺序。 - 内存管理:
WeakMap适用于需要关联一些对象的元数据,并且希望在这些对象不再被使用时自动释放内存的场景。
2.2 WeakMap 的常用方法
set(key, value):添加一个键值对,其中key必须是一个对象。
let wm = new WeakMap();
let obj = {};
wm.set(obj, 'value');
get(key):获取指定键对应的值。
console.log(wm.get(obj)); // 'value'
has(key):检查WeakMap中是否存在指定的键。
console.log(wm.has(obj)); // true
delete(key):删除指定的键值对。
wm.delete(obj);
console.log(wm.has(obj)); // false
2.3 WeakMap 的使用场景
- 元数据存储:
WeakMap常用于为对象存储元数据(例如,缓存、附加信息等)。由于其键是弱引用,WeakMap不会阻止键所引用的对象被垃圾回收,从而避免内存泄漏。 - 避免内存泄漏:在浏览器开发中,
WeakMap可以避免某些对象被不必要地保留在内存中,特别是当对象的生命周期与WeakMap中的存储数据关联时。 - 隐式对象属性:
WeakMap用于为对象提供隐式属性(如附加数据),这些属性不会暴露给外部代码,也不容易被遍历或修改。
3. Map 和 WeakMap 的主要区别
| 特性 | Map | WeakMap |
|---|---|---|
| 键的类型 | 任何类型的值(包括原始值和对象) | 只能是对象,不能是原始值(如字符串、数字等) |
| 垃圾回收 | 键值对不会被垃圾回收,除非显式删除 | 键是弱引用,当没有其他引用时,垃圾回收会自动删除键值对 |
| 内存管理 | 不会自动释放内存,需要手动清除 | 自动释放内存,不会阻止垃圾回收 |
| 迭代 | 支持迭代(forEach()、keys()、values()、entries()) | 不支持迭代,无法获取键值对 |
| 常用场景 | 适用于需要频繁增删改查的场景 | 适用于关联对象的元数据、避免内存泄漏的场景 |
总结
- Map:适合需要频繁插入、删除和访问的场景,支持任意类型的键,支持迭代,且键值对在内存中常驻,直到被显式删除。
- WeakMap:适合需要存储对象的场景,尤其是在需要避免对象因被
Map引用而无法垃圾回收的情况。由于键是弱引用,WeakMap有助于内存管理,但不支持迭代,也没有size属性。