WeakMap
深入了解 JavaScript 的 Map、Set、WeakMap 和 WeakSet
在现代 JavaScript 中,Map
、Set
、WeakMap
和 WeakSet
是用于存储数据的强大工具。它们为开发者提供了灵活高效的方式来管理集合和键值对数据。本文将详细介绍这些结构的用法、特性、适用场景和它们之间的区别。
1. Map
Map
是一种键值对的集合,与普通对象不同,Map
的键可以是任意类型。
特性
- 键的类型多样性:可以使用对象、函数、字符串等作为键。
- 有序性:
Map
会按照键值对插入的顺序迭代。 - 键值对数量:可以通过
map.size
获取。
方法
方法 | 描述 |
---|---|
set(key, value) |
添加或更新键值对 |
get(key) |
根据键获取值 |
has(key) |
检查是否存在某个键 |
delete(key) |
删除指定键 |
clear() |
删除所有键值对 |
keys() |
返回键的迭代器 |
values() |
返回值的迭代器 |
entries() |
返回键值对的迭代器 |
forEach(callback) |
遍历每个键值对,执行回调函数 |
示例
const map = new Map();
map.set('name', 'Alice');
map.set(42, 'The Answer');
map.set({ key: 'object' }, 'Object Value');
console.log(map.get('name')); // Alice
console.log(map.has(42)); // true
console.log(map.size); // 3
适用场景
- 需要复杂类型作为键:例如使用对象或函数作为键。
- 需要高效的键值对操作:比普通对象更快且有序。
- 需要频繁查询和更新数据:如缓存、配置项管理等。
2. Set
Set
是值的集合,其中的值是唯一的。
特性
- 无重复值:自动去重,适合存储独特的值。
- 有序性:保持插入值的顺序。
- 集合大小:通过
set.size
获取。
方法
方法 | 描述 |
---|---|
add(value) |
添加值 |
has(value) |
检查集合中是否存在某个值 |
delete(value) |
删除指定值 |
clear() |
删除所有值 |
values() |
返回值的迭代器(与 keys() 相同) |
entries() |
返回键值对迭代器(键和值相等) |
forEach(callback) |
遍历每个值,执行回调函数 |
示例
const set = new Set();
set.add(1);
set.add(5);
set.add(5); // 重复值会被忽略
console.log(set.has(5)); // true
console.log(set.size); // 2
set.delete(1);
console.log(set); // Set { 5 }
适用场景
- 需要唯一值集合:如用户 ID、标签列表等。
- 需要高效去重:快速过滤重复值。
- 需要简单的集合操作:如并集、交集或差集。
3. WeakMap
WeakMap
是一种专门用于对象键值对的集合,与 Map
的主要区别在于键必须是对象且弱引用。
特性
- 键必须是对象:
WeakMap
的键只能是对象,不能是基本类型。 - 弱引用:不会阻止垃圾回收,键对象失去引用时会被自动清除。
- 不可迭代:
WeakMap
无法通过迭代访问其内容。
方法
方法 | 描述 |
---|---|
set(key, value) |
添加或更新键值对 |
get(key) |
根据键获取值 |
has(key) |
检查是否存在某个键 |
delete(key) |
删除指定键 |
示例
const weakMap = new WeakMap();
let obj = { name: 'Alice' };
weakMap.set(obj, 'Some Value');
console.log(weakMap.get(obj)); // Some Value
obj = null; // 对象被垃圾回收,weakMap 自动清理
WeakMap
可用于将元数据与对象关联,而不影响对象的生命周期。这与私有成员示例非常相似,因为私有成员也是以外部的形式模拟的元数据,不参与原型继承。
这个用例可以扩展到已经创建的对象上。例如,在网页上,我们可能希望将额外的数据与 DOM 元素相关联,而 DOM 元素可能在之后访问这些数据。一种常见的做法是将数据附加为属性:
const buttons = document.querySelectorAll(".button");
buttons.forEach((button) => {
button.clicked = false;
button.addEventListener("click", () => {
button.clicked = true;
const currentButtons = [...document.querySelectorAll(".button")];
if (currentButtons.every((button) => button.clicked)) {
console.log("所有按钮被都被点击了!");
}
});
});
这种方法是有效的,但是有一些缺点:
clicked
属性是可枚举的,因此它会出现在Object.keys(button)
、for...in
循环中,等等。可以通过使用Object.defineProperty()
来缓解,但这会使代码更冗长。clicked
属性是一个普通的字符串属性,因此它可以被其他代码访问和覆盖。可以通过使用Symbol
键来缓解,但键仍然可以通过Object.getOwnPropertySymbols()
访问。
使用 WeakMap
来解决这些问题:
const buttons = document.querySelectorAll(".button");
const clicked = new WeakMap();
buttons.forEach((button) => {
clicked.set(button, false);
button.addEventListener("click", () => {
clicked.set(button, true);
const currentButtons = [...document.querySelectorAll(".button")];
if (currentButtons.every((button) => clicked.get(button))) {
console.log("所有按钮被都被点击了!");
}
});
});
这段代码里,只有能访问 clicked
的代码才能知道每个按钮的点击状态,而外部代码就不能修改这些状态。此外,如果任何按钮从 DOM 中删除,那么相应的元数据将自动进行垃圾回收。
缓存
你可以将传递给函数的对象与函数的结果相关联,从而在再次传入相同的对象时,可以返回缓存的结果而无需再次执行函数。如果该函数是纯函数(即它不会改变任何外部对象或导致其他可观察到的副作用)的话,这非常有用。
const cache = new WeakMap();
function handleObjectValues(obj) {
if (cache.has(obj)) {
return cache.get(obj);
}
const result = Object.values(obj).map(heavyComputation);
cache.set(obj, result);
return result;
}
适用场景
- 管理私有数据:如将对象实例的私有数据绑定到
WeakMap
中。 - 缓存对象:在对象可能被垃圾回收时无需手动清理缓存。
4. WeakSet
WeakSet
是值的集合,其值必须是对象并且弱引用。
特性
- 值必须是对象:
WeakSet
的值只能是对象,不能是基本类型。 - 弱引用:对象失去引用时会被自动清除。
- 不可迭代:
WeakSet
无法通过迭代访问其内容。
方法
方法 | 描述 |
---|---|
add(value) |
添加值 |
has(value) |
检查集合中是否存在某个值 |
delete(value) |
删除指定值 |
示例
const weakSet = new WeakSet();
let obj = { name: 'Alice' };
weakSet.add(obj);
console.log(weakSet.has(obj)); // true
obj = null; // 对象被垃圾回收,weakSet 自动清理
适用场景
- 跟踪对象集合:如 DOM 节点或临时对象集合。
- 防止内存泄漏:无需手动清理无用对象。
总结
特性 | Map | Set | WeakMap | WeakSet |
---|---|---|---|---|
键/值类型 | 任意类型 | 任意类型(值唯一) | 对象键 | 对象值 |
弱引用 | 否 | 否 | 是 | 是 |
可迭代性 | 是 | 是 | 否 | 否 |
用途 | 键值对存储 | 唯一值集合 | 对象键值对 | 对象值集合 |
选择适合的数据结构可以显著提升代码的性能和可读性。例如,WeakMap
和 WeakSet
非常适合管理临时数据,而 Map
和 Set
则是通用的数据存储工具。
希望本文能帮助你更好地理解和应用这些强大的工具!