一、Map
、Set
、WeakMap
、WeakSet
的基本概念
1. Map
(映射)
Map
是一种有序的键值对集合,允许任何类型的键,包括原始类型和对象。它比普通对象提供了更丰富的功能,尤其是在键值对管理方面。
-
特点:
- 键可以是任何类型(对象、函数、原始类型等)。
- 按插入顺序存储键值对。
- 支持
size
属性来获取 Map 的大小。 - 支持
set()
,get()
,has()
,delete()
等方法。
-
常见用法:
- 用来处理对象键值对,提供更加高效的插入、删除和查找操作。
- 提供迭代能力,可以按插入顺序遍历 Map。
javascriptconst map = new Map(); map.set('name', 'Alice'); map.set(1, 'Number One'); map.set(true, 'Boolean'); console.log(map.get('name')); // 输出 'Alice' console.log(map.get(1)); // 输出 'Number One' console.log(map.get(true)); // 输出 'Boolean'
2. Set
(集合)
Set
是一个集合,它可以存储唯一值(没有重复元素),值的类型可以是原始类型或对象。
-
特点:
- 存储值的唯一性:添加重复元素时,
Set
会自动去重。 - 支持迭代:可以按插入顺序遍历
Set
中的元素。 - 支持
add()
,delete()
,has()
等方法。
- 存储值的唯一性:添加重复元素时,
-
常见用法:
- 用于存储不重复的值,例如去重操作。
- 用于集合运算,如交集、并集等。
javascriptconst set = new Set(); set.add(1); set.add(2); set.add(2); // 不会被添加,因为2已经存在 console.log(set); // 输出 Set { 1, 2 }
3. WeakMap
(弱映射)
WeakMap
是一种特殊的 Map,它的键必须是对象,而且对这些键的引用是弱引用。弱引用意味着当对象没有其他引用时,WeakMap
会自动删除与该对象相关联的键值对。这使得 WeakMap
在避免内存泄漏方面非常有用。
-
特点:
- 键只能是对象,值可以是任意类型。
- 弱引用:如果
WeakMap
中的键所引用的对象被垃圾回收,WeakMap
中的键值对会被自动删除。 - 不支持迭代:因为
WeakMap
的键可能随时被垃圾回收,无法确定其顺序。
-
常见用法:
- 用于存储对象相关的数据或元数据,避免内存泄漏,特别是在 DOM 操作和事件监听中非常有用。
javascriptlet obj = {}; const weakMap = new WeakMap(); weakMap.set(obj, 'Object Metadata'); console.log(weakMap.get(obj)); // 输出 'Object Metadata' obj = null; // 键所引用的对象被垃圾回收,WeakMap 中的键值对也会被删除
4. WeakSet
(弱集合)
WeakSet
是 Set
的变种,存储的是对象的弱引用。和 Set
不同的是,WeakSet
中的元素必须是对象,当这些对象没有其他引用时,它们会被垃圾回收,从而避免内存泄漏。
-
特点:
- 只能存储对象,不能存储原始类型。
- 采用弱引用,类似于
WeakMap
,当对象没有其他引用时,它会被自动从集合中删除。 - 不支持迭代,因为元素的存在是依赖于对象是否被引用。
-
常见用法:
- 用于标记或存储对象,确保对象在没有其他引用时会自动被垃圾回收。
javascriptlet obj = {}; const weakSet = new WeakSet(); weakSet.add(obj); console.log(weakSet.has(obj)); // 输出 true obj = null; // 对象被垃圾回收
二、Map
、Set
、WeakMap
和 WeakSet
的区别
特性 | Map | Set | WeakMap | WeakSet |
---|---|---|---|---|
键(Key)类型 | 可以是任何类型(对象、原始值) | 无键(只存值) | 只能是对象 | 只能是对象 |
值(Value)类型 | 可以是任何类型 | 无值,只有元素 | 可以是任何类型 | 无值,只有元素 |
垃圾回收 | 不会自动回收 | 不会自动回收 | 键为对象时会自动回收 | 对象会自动回收 |
内存管理 | 会保留引用,可能导致内存泄漏 | 会保留引用,可能导致内存泄漏 | 通过弱引用避免内存泄漏 | 通过弱引用避免内存泄漏 |
迭代支持 | 支持 | 支持 | 不支持 | 不支持 |
常用方法 | set() , get() , has() , delete() |
add() , delete() , has() |
set() , get() , has() , delete() |
add() , has() , delete() |
三、实战应用
1. Map
的实战应用:高效查找
假设我们需要实现一个缓存系统,存储用户信息。我们可以使用 Map
来存储这些信息,利用其高效的查找和插入性能。
javascript
class Cache {
constructor() {
this.cache = new Map();
}
getUserInfo(userId) {
if (this.cache.has(userId)) {
return this.cache.get(userId);
}
// 假设从数据库加载用户信息
const userInfo = this.loadUserFromDatabase(userId);
this.cache.set(userId, userInfo);
return userInfo;
}
loadUserFromDatabase(userId) {
// 模拟数据库查询
return { userId, name: `User ${userId}` };
}
}
const cache = new Cache();
console.log(cache.getUserInfo(1)); // 第一次调用,从数据库加载
console.log(cache.getUserInfo(1)); // 第二次调用,从缓存中获取
2. Set
的实战应用:去重
Set
最常见的应用场景是去重。比如我们有一个包含重复数字的数组,想要去除重复项。
javascript
const numbers = [1, 2, 2, 3, 4, 4, 5];
const uniqueNumbers = new Set(numbers);
console.log([...uniqueNumbers]); // 输出 [1, 2, 3, 4, 5]
3. WeakMap
的实战应用:DOM 元素关联数据存储
在 Web 开发中,通常需要在 DOM 元素和相关数据之间建立关联,而不希望这些数据阻止 DOM 元素的垃圾回收。这时 WeakMap
非常有用。
javascript
const elementData = new WeakMap();
function setElementData(element, data) {
elementData.set(element, data);
}
function getElementData(element) {
return elementData.get(element);
}
let div = document.createElement('div');
setElementData(div, { id: 1, name: 'My Div' });
console.log(getElementData(div)); // 输出 { id: 1, name: 'My Div' }
div = null; // 当 div 被垃圾回收时,关联的数据会自动清除
4. WeakSet
的实战应用:标记已处理的对象
假设我们需要在遍历某个对象集合时,确保每个对象只被处理一次。我们可以使用 WeakSet
来标记已经处理过的对象。
javascript
const processedObjects = new WeakSet();
function processObject(obj) {
if (processedObjects.has(obj)) {
return; // 如果对象已经处理过,跳过当前对象
}
// 处理对象的逻辑
console.log('Processing object:', obj);
// 标记为已处理
processedObjects.add(obj);
}
let obj1 = { id: 1 };
let obj2 = { id: 2 };
let obj3 = { id: 1 };
processObject(obj1); // 处理 obj1
processObject(obj2); // 处理 obj2
processObject(obj3); // obj3 具有相同的 id,仍会被处理一次
// 后续对 obj1 和 obj3 的调用将不会触发重复处理
processObject(obj1); // 不会再次处理
processObject(obj3); // 不会再次处理
在这个例子中,WeakSet
用于标记哪些对象已经被处理过,避免重复处理,尤其是在处理大量对象时。由于 WeakSet
的元素是弱引用的,如果 obj1
或 obj2
没有其他引用,它们会被垃圾回收,WeakSet
会自动移除这些对象的记录。
四、何时使用 Map
、Set
、WeakMap
和 WeakSet
选择正确的数据结构对于优化代码的效率和可维护性至关重要。以下是一些常见的使用场景:
-
使用
Map
:- 当你需要使用对象或其他非字符串类型作为键时(比如数组、函数、对象等)。
- 需要快速查找键值对时(
Map
提供了比普通对象更好的性能,特别是在键是对象时)。 - 想要保持键值对的插入顺序时。
例如,处理用户信息、配置设置或缓存数据时,
Map
是非常合适的选择。 -
使用
Set
:- 当你需要存储一组唯一值时,特别是希望避免重复时。
- 当你需要频繁检查某个值是否已存在时,
Set
提供了 O(1) 的查找效率。 - 需要集合操作(如并集、交集、差集)时,
Set
非常适合。
例如,在处理文件去重、唯一标识符等场景时,
Set
很有用。 -
使用
WeakMap
:- 当你需要将数据与对象关联时,但不希望这个数据阻止对象被垃圾回收。
- 适用于存储 DOM 元素的附加数据、对象元数据等场景,特别是当对象的生命周期由其他部分控制时。
例如,开发 SPA(单页面应用)时,你可能需要将额外数据附加到 DOM 元素上,而不希望这些数据影响垃圾回收。
-
使用
WeakSet
:- 当你需要存储一组对象,并且希望对象在没有其他引用时能够被垃圾回收。
- 适用于对象去重、标记处理过的对象等场景,特别是在需要大量对象管理时,
WeakSet
可以有效防止内存泄漏。
例如,处理一组 DOM 元素并确保每个元素只被处理一次时,
WeakSet
非常有用。
总结
Map
、Set
、WeakMap
和 WeakSet
都是 JavaScript 提供的强大数据结构,它们在不同的应用场景中能够提供高效、灵活的解决方案。
Map
是一个高效的键值对集合,支持任何类型的键,并保持插入顺序。它适合用于需要快速查找和存储数据的场景。Set
用于存储唯一的值,自动去重,适合需要判断元素是否存在或进行集合运算的场景。WeakMap
提供弱引用机制,适用于存储与对象相关的数据,并确保在对象没有其他引用时不会阻止垃圾回收。它常用于关联对象和元数据,避免内存泄漏。WeakSet
是Set
的弱引用版本,适用于存储一组对象,并确保当对象没有其他引用时可以被垃圾回收。它适用于需要标记或去重对象的场景。