Map、WeakMap、Set、WeakSet最全攻略

672 阅读12分钟

1. Map

1.1 Map 的定义

Map 是一个包含键值对的集合,键和值都可以是任何类型。与普通的 JavaScript 对象不同,Map 的键可以是任何数据类型,而不仅仅是字符串或符号。

1.2 Map 的基本特性

  • 任意类型的键:在 Map 中,键可以是任意类型的数据,包括对象、函数、甚至其他 Map 实例。
  • 有序性Map 会按照插入的顺序存储键值对,因此可以保证迭代的顺序。
  • 键值对存储Map 存储的是键值对,键和值都可以是任何类型的对象。
  • 大小Map 有一个 size 属性,可以返回 Map 中键值对的数量。
  • 迭代Map 本身是可迭代的,支持 forEach 循环和其他迭代方法(如 for...of)。
  • 性能:与对象相比,Map 在进行大量操作时,尤其是当涉及到非字符串类型的键时,通常性能更好。

1.3 Map 的常用方法

  • set(key, value):设置键值对,如果键已存在,则更新值。
  let map = new Map();
  map.set('a', 1);
  map.set('b', 2);
  • get(key):根据键获取值,如果键不存在返回 undefined
  console.log(map.get('a')); // 1
  console.log(map.get('c')); // undefined
  • has(key):判断 Map 中是否包含指定的键。
  console.log(map.has('a')); // true
  console.log(map.has('c')); // false
  • delete(key):删除指定键的键值对,返回 truefalse,表示删除成功或键不存在。
  map.delete('a'); // true
  map.delete('c'); // false
  • clear():清空 Map 中所有的键值对。
  map.clear(); // 清空 Map
  • size:返回 Map 中键值对的数量。
  console.log(map.size); // 2
  • 迭代方法
    • forEach(callback):对 Map 中的每一个元素执行 callback 函数。javascript map.forEach((value, key) => { console.log(key, value); });
    • keys():返回一个包含所有键的迭代器。
    • values():返回一个包含所有值的迭代器。
    • entries():返回一个包含所有键值对的迭代器。

1.4 Map 示例

let map = new Map();

// 添加键值对
map.set('name', 'Alice');
map.set(1, 'Number');
map.set(true, 'Boolean');

// 获取值
console.log(map.get('name')); // Alice
console.log(map.get(1)); // Number

// 判断键是否存在
console.log(map.has('name')); // true
console.log(map.has('age')); // false

// 删除键值对
map.delete(1); // 删除键为 1 的元素
console.log(map.size); // 2

// 遍历 Map
map.forEach((value, key) => {
  console.log(key, value);
});

 

2. WeakMap

2.1 WeakMap 的定义

WeakMap 是一种类似于 Map 的数据结构,它存储的是键值对,但与 Map 不同的是,WeakMap 中的键必须是对象类型,且键是 弱引用。这意味着,WeakMap 不会阻止其键对象被垃圾回收。

2.2 WeakMap 的基本特性

  • 键必须是对象WeakMap 的键只能是对象类型(包括数组、函数等),不能是基本数据类型(如字符串、数字等)。
  • 弱引用WeakMap 中的键是弱引用,这意味着如果没有其他引用指向这个对象,垃圾回收器可以回收这些键值对。
  • 不支持迭代WeakMap 不支持像 Map 一样进行迭代,因为它的键是弱引用,可能会被垃圾回收,因此无法保证迭代顺序。
  • 内存管理WeakMap 可以帮助管理内存,尤其是在存储大量数据时,可以避免不再使用的对象继续占用内存。

2.3 WeakMap 的常用方法

  • set(key, value):设置键值对,键必须是对象。
  let weakMap = new WeakMap();
  let obj = {};
  weakMap.set(obj, 'Some value');
  • get(key):根据键获取值,如果键不存在返回 undefined
  console.log(weakMap.get(obj)); // 'Some value'
  • has(key):判断 WeakMap 中是否包含指定的键。
  console.log(weakMap.has(obj)); // true
  • delete(key):删除指定键的键值对,返回 truefalse,表示删除成功或键不存在。
  weakMap.delete(obj); // true

2.4 WeakMap 示例

let weakMap = new WeakMap();
let obj1 = {};
let obj2 = {};

// 添加键值对
weakMap.set(obj1, 'Value 1');
weakMap.set(obj2, 'Value 2');

// 获取值
console.log(weakMap.get(obj1)); // 'Value 1'

// 删除键值对
weakMap.delete(obj1);
console.log(weakMap.has(obj1)); // false

// 垃圾回收:当 obj1 没有其他引用时,weakMap 中的键会自动被删除

 

3. Map 与 WeakMap 的区别

特性MapWeakMap
键的类型可以是任何类型(包括对象、函数等)键必须是对象类型
垃圾回收键值对不会被垃圾回收机制影响键是弱引用,可以被垃圾回收
支持迭代支持 forEach、keys()、values()、entries()不支持迭代
大小可以通过 .size 获取大小不支持获取大小
内存管理不适用于内存敏感应用更适合内存管理,避免对象泄漏
  • Map:适合需要频繁插入、删除和访问的场景,支持任意类型的键,支持迭代,且键值对在内存中常驻,直到被显式删除。
  • WeakMap:适合需要存储对象的场景,尤其是在需要避免对象因被 Map 引用而无法垃圾回收的情况。由于键是弱引用,WeakMap 有助于内存管理,但不支持迭代,也没有 size 属性。

4. Set

Set 是 ES6 引入的一种集合数据结构,用于存储一组唯一值,没有重复元素。

4.1 Set 的基本特点

  • 唯一性Set 中的元素是唯一的,不能重复。如果你尝试插入重复的元素,它会自动忽略。
  • 顺序性Set 保留插入元素的顺序。你插入的顺序就是遍历时的顺序。
  • 可迭代Set 是可迭代的,支持 for...of 和其他迭代方法进行遍历。
  • 类型Set 可以存储任何类型的值,包括原始类型(如数字、字符串)和对象类型(如数组、对象)。
  • 无索引访问:不像数组,Set 不支持通过索引访问元素。

4.2 常用方法

  1. add(value):向 Set 中添加一个元素。如果该元素已存在,Set 会忽略它。
   let set = new Set();
   set.add(1);
   set.add(2);
   set.add(1);  // 不会添加,重复的元素会被忽略
   console.log(set);  // Set { 1, 2 }
  1. has(value):检查 Set 中是否包含指定的元素。
   console.log(set.has(1));  // true
   console.log(set.has(3));  // false
  1. delete(value):删除 Set 中的指定元素。
   set.delete(1);
   console.log(set);  // Set { 2 }
  1. clear():清空 Set 中的所有元素。
   set.clear();
   console.log(set);  // Set {}
  1. size:返回 Set 中元素的数量。
   console.log(set.size);  // 2
  1. forEach(callback):对 Set 中的每个元素执行一个回调函数。
   set.forEach(value => console.log(value));  // 输出 1 和 2
  1. values():返回一个包含 Set 中所有值的迭代器对象。
   let iterator = set.values();
   console.log(iterator.next().value);  // 1

4.3 Set 的使用场景

  • 去重Set 最常见的用途之一是去重。通过将一个数组转换为 Set,我们可以轻松去除其中的重复元素。
  let arr = [1, 2, 2, 3, 4, 4, 5];
  let uniqueArr = [...new Set(arr)];
  console.log(uniqueArr);  // [1, 2, 3, 4, 5]
  • 集合运算Set 可以很方便地进行集合的交集、并集和差集等运算。
    • 并集:可以通过将两个 Set 合并来实现并集。
    • 交集:通过过滤操作来获取交集。
    • 差集:通过过滤操作来获取差集。

 

5. WeakSet

WeakSetSet 的一个变种,主要用于存储对象的引用,并且这些引用是弱引用,意味着当没有其他地方引用这些对象时,它们会被自动清除。

5.1 WeakSet 的基本特点

  • 只能存储对象WeakSet 只能存储对象,不能存储原始数据类型(如数字、字符串、布尔值等)。
  • 弱引用WeakSet 中的元素是弱引用的。如果没有其他引用指向这些对象,它们会在垃圾回收时自动删除。
  • 不支持遍历WeakSet 没有提供迭代器方法,如 forEach()values()keys() 等,因此不能像 Set 一样遍历元素。
  • 没有 clear() 方法:由于元素是弱引用的,WeakSet 不需要清除操作,它们会在没有其他引用时被垃圾回收。

5.2 常用方法

  1. add(value):向 WeakSet 中添加一个对象。元素必须是对象类型。
   let ws = new WeakSet();
   let obj = {};
   ws.add(obj);
  1. has(value):检查 WeakSet 中是否包含指定的对象。
   console.log(ws.has(obj));  // true
  1. delete(value):删除 WeakSet 中的指定对象。
   ws.delete(obj);
   console.log(ws.has(obj));  // false

5.3 WeakSet 的使用场景

内存管理WeakSet 在内存管理中非常有用,因为它允许对象在没有其他引用时被垃圾回收。它非常适合用于存储和跟踪需要在某个时刻“标记”的对象。

  • 比如,你可以用 WeakSet 来标记某个对象是否已被处理,但不会阻止这些对象被垃圾回收。
  let ws = new WeakSet();
  let obj1 = {};
  let obj2 = {};

  ws.add(obj1);
  console.log(ws.has(obj1));  // true

  obj1 = null;  // 释放 obj1 的引用
  // obj1 被垃圾回收,不再出现在 WeakSet 中
  • 避免内存泄漏WeakSet 很适合用来避免内存泄漏,特别是在事件监听或对象管理的场景中,防止对象无法被回收。

 

5.4 Set 和 WeakSet 的对比

特性SetWeakSet
存储类型可以存储任何类型(原始值或对象)只能存储对象
引用方式强引用,元素不会被垃圾回收弱引用,元素会在没有其他引用时被垃圾回收
迭代支持迭代(forEach()、values() 等)不支持迭代操作(无法使用 forEach() 等)
内存管理元素始终存在,直到手动删除自动释放内存,当没有其他引用时会被垃圾回收
应用场景去重、集合运算、缓存、处理唯一值等对象标记、内存管理、避免内存泄漏
  • 支持的数据类型Set 可以存储任何类型的值,而 WeakSet 只能存储对象。
  • 垃圾回收Set 中的元素是强引用,不会被垃圾回收,而 WeakSet 中的元素是弱引用,能在没有其他引用时被自动回收。
  • 是否支持遍历Set 支持遍历,而 WeakSet 不支持遍历。这是因为 WeakSet 的元素是弱引用,不能保证它们会一直存在。
  • 内存管理WeakSet 用于管理对象的生命周期,适合用于标记和追踪对象而不会影响它们的垃圾回收。

5.5 使用选择

  • 使用 Set 当你需要存储任意类型的唯一值时,并且需要对这些值进行迭代和其他集合操作。
  • 使用 WeakSet 当你需要存储对象的弱引用,并且不需要对这些对象进行迭代或操作时,特别是在避免内存泄漏的场景中。

6. Map、WeakMap、Set、WeakSet总结

特性/集合类型MapWeakMapSetWeakSet
存储数据键值对(key-value)键值对(key-value),但键是弱引用唯一值集合(无重复元素)唯一值集合(无重复元素),且值是对象
键类型可以是任意数据类型(原始值、对象、函数等)键必须是对象(弱引用)可以是任意数据类型(原始值、对象、函数等)只能是对象(弱引用)
值类型可以是任意数据类型(原始值、对象、函数等)值可以是任意数据类型(原始值、对象、函数等)可以是任意数据类型(原始值、对象、函数等)可以是任意数据类型,但元素必须是对象
垃圾回收键和值都不会被垃圾回收机制自动清除键是弱引用,只有当对象没有其他引用时才会被回收不涉及垃圾回收,值保持直到手动删除键是弱引用,且只有对象作为值,当对象没有其他引用时,会被垃圾回收
大小可通过 size 获取集合大小size 属性,不能直接获取集合大小可通过 size 获取集合大小size 属性,不能直接获取集合大小
遍历支持支持遍历(forEach, for-of, keys(), values(), entries()不支持遍历支持遍历(forEach, for-of, values(), keys(), entries()不支持遍历
更新操作支持更新键值对(set()delete()clear()不支持更新键值对,只能 set() 新增键值对或 delete() 删除支持添加、删除元素(add()delete()clear()支持添加、删除元素(add()delete()clear()
性能查找性能优于普通对象,适合大量数据存储和快速查找由于弱引用机制,适合处理需要垃圾回收的对象数据查找性能较好,适用于去重操作适合用于存储对象引用,避免内存泄漏
用途场景存储键值对,适合需要频繁更新和查找的场景存储与对象相关的元数据,防止内存泄漏存储唯一值,去重操作,高效查找追踪对象引用,防止内存泄漏

6.1 对比

相同点

  • MapWeakMap 都是存储键值对的数据结构,而 SetWeakSet 存储的是唯一的值。
  • SetWeakSet 不允许元素重复;MapWeakMap 中的键是唯一的。
  • WeakMapWeakSet 都是基于弱引用来处理数据,因此可以避免内存泄漏的问题。
  • 所有四种数据结构都支持 delete() 方法来删除特定元素。

不同点

  • 键/值MapWeakMap 都存储键值对,SetWeakSet 只存储值。
  • 数据类型限制WeakMapWeakSet 的键/值必须是对象,而 MapSet 的元素可以是任何类型。
  • 垃圾回收WeakMapWeakSet 利用弱引用机制,垃圾回收时会自动清理不再被引用的对象;而 MapSet 没有这种机制。
  • 遍历支持MapSet 都支持遍历操作(可以使用 forEach 或者 for-of),但 WeakMapWeakSet 由于使用弱引用,不支持遍历。

6.2 应用场景

Map

  • 缓存:适合用于存储键值对,尤其是当键可以是复杂对象或函数时。
  • 关联数组:需要根据特定键查找、更新或删除值时,Map 是一个优秀的选择。
  • 数据存储与查询:例如用户设置、存储对象与属性映射等场景。

WeakMap

  • 防止内存泄漏:当需要关联对象时(例如将附加数据存储在 DOM 元素上),WeakMap 可以避免内存泄漏,因为它会自动清理不再使用的键。
  • 私有数据:可以通过 WeakMap 模拟私有属性,只能通过对象的引用访问。

Set

  • 去重:使用 Set 可以方便地去除数组中的重复元素。
  • 集合运算Set 可以用于表示数学中的集合,进行交集、并集和差集运算。
  • 快速查找:当需要频繁检查某个值是否存在于集合中时,Set 提供了高效的查找操作。

WeakSet

  • 对象引用管理:用于管理对象引用,确保对象在不再使用时可以被垃圾回收。
  • 避免内存泄漏:适用于跟踪 DOM 元素或其他对象的引用,并在对象不再被引用时自动清理内存。
  • 资源回收:如 DOM 元素、事件监听器等,当对象不再使用时自动释放内存。