1. 弱映射
首先,我们先来了解一下映射(Map)。在JavaScript中,映射是一种数据结构,它允许我们将一个值与一个键相关联。通俗点说,就是在JavaScript中,映射就是一种存储键值对的数据结构,你可以随时添加修改删除键值对。
但是,在ES6之前,JavaScript中的映射是强映射(StrongMap),也就是说只要持有了键,我们就可以读取或修改映射中与这个键相关的值。这就造成了一些不便,如当键被销毁时,由于只是检查内部的键集合,因此实际上并没有一种良好的方法来清除相应的值。
为了解决这个问题,ES6引入了弱映射(WeakMap)。它的键是弱引用,这意味着在没有其他引用存在时,它们可以被垃圾回收器回收。
1.1 弱映射的基本使用
下面是一个简单的例子,它演示了如何创建一个弱映射并向其中添加元素:
let weakMap = new WeakMap();
let obj = {};
weakMap.set(obj, "hello world");
console.log(weakMap.get(obj)); // "hello world"
这里声明了一个弱映射weakMap
,用于存储对象obj
和字符串"hello world"之间的关系。通过set
方法,将对象obj
和字符串"hello world"关联起来。
接下来,我们调用get
方法并传入obj
作为参数,返回与obj
相关联的值。由于它们已经被关联在一起,程序将输出字符串"hello world"。
1.2 弱映射的优势
在了解弱映射的基本用法后,我们来看看它的优势所在。
弱映射有一个非常重要的特点,就是如果引用一个对象的唯一方式是通过一个映射,那么Javascript引擎可以自动清除不再使用的对象,从而释放内存。
为了更清晰的了解弱映射的优势,我们通过一个例子来说明这点:
let company = {
name: "Google"
};
let employee1 = {
name: "John"
};
let employee2 = {
name: "Alice"
};
let employee3 = {
name: "Bob"
};
let companyEmployees = new WeakMap();
companyEmployees.set(employee1, company);
companyEmployees.set(employee2, company);
companyEmployees.set(employee3, company);
employee1 = null;
employee2 = null;
上述代码中,我们声明了一个名为company
的对象,然后又声明了几个名为employee1、employee2、employee3
的对象。
接下来,我们创建了一个名为companyEmployees
的弱映射,并将这三个对象和company
对象关联起来。然后将变量employee1、employee2
设置为null
,释放了对employee1、employee2
的引用。
在上述代码中,由于employee1
和employee2
是null
,它们不再被使用。因此,它们会被垃圾回收器自动清除,从而释放内存。
1.3 弱映射的注意事项
虽然弱映射提供了很多优势,但是我们在使用的时候还是需要时刻注意以下几点:
弱映射的键必须是对象,否则将会抛出TypeError。
弱映射的键不能被枚举,所以不能使用for...in
循环或Object.keys
等方法获取弱映射中的键。
如果弱映射中的键被垃圾回收器回收,那么这些键值对也将从弱映射中被自动删除。
2. 弱集合
正如我们之前所提到的,弱集合与弱映射类似。不同的是,它们存储的是唯一的,而不是键-值对。
2.1 弱集合的基本使用
下面是一个简单的例子,它演示了如何创建一个弱集合并向其中添加元素:
let weakSet = new WeakSet();
let obj1 = {};
weakSet.add(obj1);
let obj2 = {};
weakSet.add(obj2);
console.log(weakSet.has(obj1)); // true
console.log(weakSet.has(obj2)); // true
这里首先声明一个弱集合weakSet
,用于存储对象obj1
和obj2
。然后,我们通过add
方法,将这两个对象添加到了weakSet
中。
最后,我们通过has
方法检查obj1
和obj2
是否在弱集合weakSet
中,输出结果为true
。
2.2 弱集合的注意事项
虽然弱集合提供了很多优势,但是我们在使用的时候还是需要时刻注意以下几点:
弱集合中的元素必须是对象,否则将会抛出TypeError。
弱集合中的元素不能被枚举,所以不能使用for...in
循环或Object.keys
等方法获取弱集合中的元素。
如果弱集合中的元素被垃圾回收器回收,那么这些元素也将从弱集合中被自动删除。
2.3 弱映射和弱集合的适用场景
对于弱映射和弱集合,它们并不是总是适用于所有情况。相反,它们只适用于特定场景。
在JavaScript中,这两种结构通常用于保存敏感信息。弱映射和弱集合确保在对象不再被引用时,不会暴露这些信息。
2.4 弱映射、弱集合与反向引用
在ES6之前,我们需要使用一些额外的技术来解决引用、循环依赖和内存泄漏等问题。而现在,我们可以使用弱映射和弱集合来解决这些问题。
例如,有时候我们需要建立对象之间的反向引用,但是这样会导致内存泄漏。在这种情况下,我们可以使用弱映射和弱集合来防止内存泄漏。
下面是一个例子,它演示了如何使用弱映射来建立反向引用以及如何使用弱集合来避免内存泄漏:
let john = { name: 'John' };
let alice = { name: 'Alice' };
let friends = new Map();
friends.set(john, alice);
let reverseFriends = new WeakMap();
reverseFriends.set(alice, john);
john = null;
// 内存泄漏:alice 依然被引用,不能被 GC 回收
console.log(friends.get(john)); // undefined
console.log(reverseFriends.get(alice)); // { name: 'John' }
let weakSet = new WeakSet();
weakSet.add(john);
weakSet.add(alice);
john = null;
// 可以被 GC 回收
console.log(weakSet.has(john)); // false
console.log(weakSet.has(alice)); // true
在上述代码中,我们声明了对象john
和alice
。然后,我们创建了一个Map
对象friends
,用于存储对象john
和对象alice
之间的关系。接着,我们创建一个WeakMap
对象reverseFriends
,与friends
相反,用于存储alice
和john
之间的关系。
接下来,我们将变量john
设置为null
,释放了对对象john
的引用。由于对象john
被释放,因此我们无法通过friends
来获取john
和alice
之间的关系。
但是,我们通过reverseFriends
仍然可以访问到他们之间的关系。这是因为在创建reverseFriends
时,对象alice
被弱引用,因此当引用不再存在时,对象将被从reverseFriends
中删除。
而弱集合也有类似的用途。在上述代码中,我们通过使用WeakSet
来确保对象john
可以被垃圾回收器回收。如果我们只是将对象john
添加到一个强集合中,那么当变量john
被设置为null
时,它仍然会被WeakMap
或Map
等集合引用,这将导致内存泄漏。
2.5 弱映射和弱集合的性能问题
虽然弱映射和弱集合提供了很多优势,但是它们的性能可能不如传统的Map
和Set
。这是由于它们需要更多的额外内存来保存信息。
在使用弱映射和弱集合时,我们需要时刻根据具体场景,权衡它们的优劣势,并选择最适合的一种。如果希望在内存和性能之间进行权衡,那么强集合可能是更好的选择。