JavaScript 中的 Map、WeakMap、Set 详解

353 阅读3分钟

JavaScript 中的 Map、WeakMap、Set 详解

在 JavaScript 中,MapWeakMapSet 是用于存储数据集合的特殊对象类型,它们各有特点和应用场景。下面我将详细解释它们的用法并提供具体案例。

1. Map(映射)

Map 是一种键值对集合,其中键可以是任意类型(对象、原始值等)。

基本用法

// 创建 Map
const userMap = new Map();

// 添加键值对
userMap.set('name', 'Alice');
userMap.set(1, 'ID');
userMap.set({ role: 'admin' }, '权限对象');

// 获取值
console.log(userMap.get('name')); // 'Alice'
console.log(userMap.get(1));      // 'ID'

// 检查键是否存在
console.log(userMap.has('name')); // true

// 删除键值对
userMap.delete(1);

// 获取大小
console.log(userMap.size); // 2

// 遍历 Map
userMap.forEach((value, key) => {
  console.log(`${key}: ${value}`);
});

// 清空 Map
userMap.clear();

使用对象作为键

const user1 = { id: 1 };
const user2 = { id: 2 };

const preferences = new Map();
preferences.set(user1, { theme: 'dark', lang: 'en' });
preferences.set(user2, { theme: 'light', lang: 'fr' });

console.log(preferences.get(user1).theme); // 'dark'

与普通对象的区别

特性Map普通对象
键的类型任意类型String/Symbol
顺序插入顺序无序
大小.size 属性手动计算
性能频繁增删时更优相对较慢
默认键有原型链属性

2. WeakMap(弱映射)

WeakMap 与 Map 类似,但有重要区别:

  • 必须是对象
  • 键是弱引用(不阻止垃圾回收)
  • 不可遍历(没有迭代方法)

基本用法

const weakMap = new WeakMap();

let obj1 = { id: 1 };
let obj2 = { id: 2 };

// 设置键值对
weakMap.set(obj1, '私有数据1');
weakMap.set(obj2, '私有数据2');

// 获取值
console.log(weakMap.get(obj1)); // '私有数据1'

// 删除
weakMap.delete(obj1);

// 检查存在
console.log(weakMap.has(obj2)); // true

典型应用:私有成员

const privateData = new WeakMap();

class User {
  constructor(name) {
    // 每个实例的私有数据存储在WeakMap中
    privateData.set(this, { name });
  }
  
  getName() {
    return privateData.get(this).name;
  }
}

const user = new User('Alice');
console.log(user.getName()); // 'Alice'
console.log(privateData.get(user)); // { name: 'Alice' }
console.log(user.name); // undefined(真正私有)

WeakMap 特点

  • 当键对象被垃圾回收时,对应的值也会自动清除
  • 没有 size 属性
  • 不能使用 forEach()for...of 遍历
  • 不支持清空整个 WeakMap

3. Set(集合)

Set 是唯一值的集合,每个值只能出现一次。

基本用法

const uniqueNumbers = new Set();

// 添加值
uniqueNumbers.add(1);
uniqueNumbers.add(2);
uniqueNumbers.add(2); // 重复值会被忽略
uniqueNumbers.add('text');
uniqueNumbers.add({ id: 1 });

// 检查存在
console.log(uniqueNumbers.has(1)); // true

// 获取大小
console.log(uniqueNumbers.size); // 4

// 删除值
uniqueNumbers.delete('text');

// 遍历 Set
uniqueNumbers.forEach(value => {
  console.log(value);
});

// 转换为数组
const numbersArray = [...uniqueNumbers];

// 清空
uniqueNumbers.clear();

常见应用场景

1. 数组去重

const duplicates = [1, 2, 2, 3, 4, 4, 5];
const unique = [...new Set(duplicates)];
console.log(unique); // [1, 2, 3, 4, 5]

2. 求交集/并集/差集

const setA = new Set([1, 2, 3]);
const setB = new Set([2, 3, 4]);

// 并集
const union = new Set([...setA, ...setB]);

// 交集
const intersection = new Set([...setA].filter(x => setB.has(x)));

// 差集 (A - B)
const difference = new Set([...setA].filter(x => !setB.has(x)));

console.log([...union]);        // [1, 2, 3, 4]
console.log([...intersection]); // [2, 3]
console.log([...difference]);   // [1]

3. 跟踪唯一对象

const visitedNodes = new Set();

function traverse(node) {
  if (visitedNodes.has(node)) return;
  
  visitedNodes.add(node);
  // 处理节点...
  for (let child of node.children) {
    traverse(child);
  }
}

对比总结表

特性MapWeakMapSet
键类型任意类型必须是对象存储值(无键)
值类型任意类型任意类型唯一值
可迭代
大小.size 属性不可获取.size 属性
垃圾回收强引用(阻止回收)弱引用(不阻止回收)强引用(阻止回收)
遍历方法forEach, for...of, keysforEach, for...of
主要用途键值对存储(键可为对象)对象关联元数据/私有成员唯一值集合/数学集合操作
性能增删查 O(1)增删查 O(1)增删查 O(1)

什么时候使用?

  • 使用 Map 当需要:

    • 键值对集合且键不是字符串
    • 需要保持插入顺序
    • 需要频繁添加/删除键值对
  • 使用 WeakMap 当需要:

    • 关联对象与元数据而不影响垃圾回收
    • 实现真正的私有属性
    • 存储对象特定的元信息
  • 使用 Set 当需要:

    • 存储唯一值
    • 快速检查值是否存在
    • 执行集合操作(并集、交集等)

这些集合类型为JavaScript提供了更强大的数据结构处理能力,根据具体场景选择合适的类型可以写出更高效、更安全的代码。