Map、Set、WeakMap、WeakSet 的区别

ES6 四种集合类型的特性对比与使用场景

问题

Map、Set、WeakMap、WeakSet 有什么区别?分别在什么场景下使用?

解答

Set - 值的集合

存储唯一值,自动去重。

const set = new Set();

// 添加值
set.add(1);
set.add(2);
set.add(2); // 重复值会被忽略

console.log(set.size); // 2
console.log(set.has(1)); // true

// 遍历
for (const value of set) {
  console.log(value); // 1, 2
}

// 数组去重
const arr = [1, 2, 2, 3, 3, 3];
const unique = [...new Set(arr)]; // [1, 2, 3]

Map - 键值对集合

键可以是任意类型,不限于字符串。

const map = new Map();

// 任意类型作为键
const objKey = { id: 1 };
const fnKey = () => {};

map.set(objKey, 'object value');
map.set(fnKey, 'function value');
map.set('name', 'string value');

console.log(map.get(objKey)); // 'object value'
console.log(map.size); // 3

// 遍历
for (const [key, value] of map) {
  console.log(key, value);
}

// 初始化时传入数组
const map2 = new Map([
  ['a', 1],
  ['b', 2]
]);

WeakSet - 弱引用值集合

只能存储对象,不阻止垃圾回收。

const weakSet = new WeakSet();

let obj = { name: 'test' };
weakSet.add(obj);

console.log(weakSet.has(obj)); // true

// obj 被置空后,WeakSet 中的引用会被自动回收
obj = null;
// weakSet 中的对象会在下次 GC 时被清除

// 不能存储原始值
// weakSet.add(1); // TypeError

// 不可遍历,没有 size 属性
// weakSet.forEach() // 不存在
// weakSet.size // undefined

WeakMap - 弱引用键集合

键必须是对象,键的引用不阻止垃圾回收。

const weakMap = new WeakMap();

let element = document.getElementById('app');

// 存储 DOM 元素相关数据
weakMap.set(element, {
  clickCount: 0,
  bindTime: Date.now()
});

console.log(weakMap.get(element)); // { clickCount: 0, bindTime: ... }

// 当 element 从 DOM 移除且无其他引用时
// WeakMap 中的条目会自动清除,避免内存泄漏
element = null;

// 键必须是对象
// weakMap.set('key', 'value'); // TypeError

对比表格

特性SetMapWeakSetWeakMap
存储内容键值对值(仅对象)键值对(键仅对象)
键/值类型任意任意对象键为对象
可遍历
有 size
弱引用

使用场景

// 1. Set:去重、集合运算
const setA = new Set([1, 2, 3]);
const setB = new Set([2, 3, 4]);

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

// 并集
const union = new Set([...setA, ...setB]); // {1, 2, 3, 4}

// 2. Map:需要非字符串键、保持插入顺序
const userRoles = new Map();
const user1 = { id: 1 };
userRoles.set(user1, ['admin', 'editor']);

// 3. WeakSet:标记对象,不影响 GC
const visited = new WeakSet();

function process(obj) {
  if (visited.has(obj)) {
    return; // 已处理过
  }
  visited.add(obj);
  // 处理逻辑...
}

// 4. WeakMap:存储对象私有数据、缓存
const privateData = new WeakMap();

class User {
  constructor(name, password) {
    this.name = name;
    // 私有数据存在 WeakMap,外部无法访问
    privateData.set(this, { password });
  }
  
  checkPassword(pwd) {
    return privateData.get(this).password === pwd;
  }
}

const user = new User('tom', '123456');
console.log(user.name); // 'tom'
console.log(user.password); // undefined
console.log(user.checkPassword('123456')); // true

关键点

  • Set 存唯一值,Map 存键值对,键可以是任意类型
  • Weak 版本只能用对象作为键/值,且是弱引用
  • 弱引用不阻止垃圾回收,适合存储 DOM 元素或对象的附加数据
  • Weak 版本不可遍历、没有 size,因为内容随时可能被回收
  • WeakMap 常用于实现私有属性和缓存