您的位置:

深入理解JavaScript中的WeakMap

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的集合类型之一的弱引用集合。