WeakMap是ES2015中新增的一种集合类型,它的设计目标是提供一种弱引用的数据结构,它的主要用途是用于实现一些对垃圾回收模型敏感的功能,比如缓存、事件监听等。本文将从多个方面来详细讲解WeakMap的特性和使用场景。
一、什么是WeakMap
首先,我们需要了解什么是Map。Map是一种使用键值对储存数据的集合类型,其中键和值可以是任何类型(包括引用类型),并且可以使用for...of循环进行遍历。与之相对的WeakMap则是一种只能使用对象作为键的集合类型,而且对象键对应的对象值只能通过对象本身来访问,无法通过其他方式访问。
const map = new Map();
const obj1 = {name: "John"};
const obj2 = {name: "Lucy"};
map.set(obj1, "Hello");
map.set(obj2, "World");
console.log(map.get(obj1)); // "Hello"
console.log(map.get(obj2)); // "World"
const weakMap = new WeakMap();
const obj3 = {};
const obj4 = {};
weakMap.set(obj3, "Hello");
weakMap.set(obj4, "World");
console.log(weakMap.get(obj3)); // "Hello"
console.log(weakMap.get(obj4)); // "World"
可以看到,WeakMap可以像Map一样储存键值对,不同之处是WeakMap的键只能是对象,并且这些对象键所对应的值只能通过这些对象本身来访问,不能像Map一样通过键本身来访问。
二、WeakMap的弱引用特性
WeakMap之所以被称为“弱引用”,是因为它的键实际上是一种弱引用。在JavaScript中,内存管理是由垃圾回收器来负责的,而垃圾回收器的主要判断依据就是对象是否被引用。如果一个对象没有被任何引用所引用,那么它就会被判定为垃圾对象,并被回收。
在WeakMap中,如果一个键对应的对象被回收了,那么这个键所对应的键值对也会被自动删除。这意味着,如果使用WeakMap来储存一些内存占用较大的对象,当这些对象不再被引用时,它们也会被自动回收,从而释放内存空间。
const weakMap = new WeakMap();
(function() {
const obj = {name: "John"};
weakMap.set(obj, "Hello");
})();
// obj被回收了,对应的键值对也被删除了
console.log(weakMap.size); // undefined
在上面的代码中,我们使用立即执行函数来创建一个包含一个对象的作用域,在这个作用域中,我们将这个对象作为WeakMap的键储存了起来,然后立即销毁了这个作用域。由于obj对象在该作用域外没有任何引用,因此它会被垃圾回收器判定为垃圾对象,并被自动回收。在这个过程中,WeakMap中对应的键值对也会被删除。
三、常见应用场景
1. 实现私有属性
在JavaScript中,没有真正意义上的私有属性。通过使用WeakMap,我们可以实现一种近似私有属性的机制。我们可以利用WeakMap的弱引用特性,将某个实例对象作为键,将需要储存的私有属性作为键值储存在WeakMap中。由于对该实例对象的引用仅存在于该实例对象本身中,因此除了该实例对象本身,没有任何其他途径可以访问到其对应的私有属性。
const privateData = new WeakMap();
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
privateData.set(this, {salary: 5000});
}
getSalary() {
return privateData.get(this).salary;
}
}
const person = new Person("John", 20);
console.log(person.name); // "John"
console.log(person.getSalary()); // 5000
console.log(privateData.get(person)); // undefined
2. 缓存
缓存是一种常见的技术,用于提高程序的运行效率。由于WeakMap的弱引用特性,我们可以在不占用过多内存的前提下实现缓存功能。我们可以将某个需要缓存的对象作为键,将它的计算结果作为键值储存在WeakMap中。在后续使用时,我们可以根据需要重新计算并存入WeakMap中,这可以避免缓存造成的内存占用问题。
const cache = new WeakMap();
function factorial(n) {
if(n === 0) {
return 1;
}
if(cache.has(n)) {
console.log("Cache hit!");
return cache.get(n);
}
const result = n * factorial(n - 1);
cache.set(n, result);
console.log("Cache miss!");
return result;
}
console.log(factorial(5)); // Cache miss! 120
console.log(factorial(5)); // Cache hit! 120
3. 存储DOM节点相关数据
在JavaScript中,我们可以使用dataset属性或者一些自定义属性来实现存储一些与Dom节点相关的数据,但是这会引发一些垃圾回收方面的问题。使用WeakMap来储存这些数据,则可以避免这些问题。
const nodeCache = new WeakMap();
function setAttribute(node, name, value) {
if(!nodeCache.has(node)) {
nodeCache.set(node, {});
}
nodeCache.get(node)[name] = value;
}
function getAttribute(node, name) {
return nodeCache.has(node) ? nodeCache.get(node)[name] : undefined;
}
const div = document.createElement("div");
setAttribute(div, "data-name", "John");
console.log(getAttribute(div, "data-name")); // "John"
四、总结
本文详细介绍了JavaScript中的WeakMap,并从WeakMap的定义、弱引用特性、常见应用场景等多个方面进行了阐述。希望读者们可以通过本文更深入地了解JavaScript的集合类型之一的弱引用集合。