WeakMap

深入了解 JavaScript 的 Map、Set、WeakMap 和 WeakSet

在现代 JavaScript 中,MapSetWeakMapWeakSet 是用于存储数据的强大工具。它们为开发者提供了灵活高效的方式来管理集合和键值对数据。本文将详细介绍这些结构的用法、特性、适用场景和它们之间的区别。


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("所有按钮被都被点击了!");
    }
  });
});

这种方法是有效的,但是有一些缺点:

使用 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
键/值类型 任意类型 任意类型(值唯一) 对象键 对象值
弱引用
可迭代性
用途 键值对存储 唯一值集合 对象键值对 对象值集合

选择适合的数据结构可以显著提升代码的性能和可读性。例如,WeakMapWeakSet 非常适合管理临时数据,而 MapSet 则是通用的数据存储工具。

希望本文能帮助你更好地理解和应用这些强大的工具!

苏ICP备2025153828号