JavaScript 引擎在值可访问(并可能被使用)时将其存储在内存中:

  1. let john = { name: "John" };
  2. // 该对象能被访问,john 是它的引用
  3. // 覆盖引用
  4. john = null;
  5. // 该对象将会被从内存中清除

通常,当对象、数组这类数据结构在内存中时,它们的子元素,如对象的属性、数组的元素都是可以访问的。
例如,如果把一个对象放入到数组中,那么只要这个数组存在,那么这个对象也就存在,即使没有其他对该对象的引用。

  1. let john = { name: "John" };
  2. let array = [ john ];
  3. john = null; // 覆盖引用
  4. // 前面由 john 所引用的那个对象被存储在了 array 中
  5. // 所以它不会被垃圾回收机制回收

类似的,如果我们使用对象作为常规 Map 的键,那么当 Map 存在时,该对象也将存在。它会占用内存,并且应该不会被(垃圾回收机制)回收。

  1. let john = { name: "John" };
  2. let map = new Map();
  3. map.set(john, "...");
  4. john = null; // 覆盖引用
  5. // john 被存储在了 map 中,
  6. // 我们可以使用 map.keys() 来获取它

WeakMap 在这方面有着根本上的不同。它不会阻止垃圾回收机制对作为键的对象(key object)的回收。

WeakMap

WeakMap 和 Map 的第一个不同点就是,WeakMap 的键必须是对象,不能是原始值:

  1. let weakMap = new WeakMap();
  2. let obj = {};
  3. weakMap.set(obj, "ok"); // 正常工作(以对象作为键)
  4. // 不能使用字符串作为键
  5. weakMap.set("test", "Whoops"); // Error,因为 "test" 不是一个对象

在 weakMap 中使用一个对象作为键,并且没有其他对这个对象的引用 —— 该对象将会被从内存(和map)中自动清除。

  1. let john = { name: "John" };
  2. let weakMap = new WeakMap();
  3. weakMap.set(john, "...");
  4. john = null; // 覆盖引用
  5. // john 被从内存中删除了!weakMap里也自动删除了这个键

与上面常规的 Map 的例子相比,现在如果 john 仅仅是作为 WeakMap 的键而存在 —— 它将会被从 WeakMap(和内存)中自动删除。

WeakMap 不支持迭代以及 keys(),values() 和 entries() 方法。所以没有办法获取 WeakMap 的所有键或值。
WeakMap 只有以下的方法:

  • weakMap.get(key)
  • weakMap.set(key, value)
  • weakMap.delete(key)
  • weakMap.has(key)

    使用案例:额外的数据

    WeakMap 的主要应用场景是 额外数据的存储
    假如我们正在处理一个“属于”另一个代码的一个对象,也可能是第三方库,并想存储一些与之相关的数据,那么这些数据就应该与这个对象共存亡 —— 这时候 WeakMap 正是我们所需要的利器。
    我们将这些数据放到 WeakMap 中,并使用该对象作为这些数据的键,那么当该对象被垃圾回收机制回收后,这些数据也会被自动清除。
    1. weakMap.set(john, "secret documents");
    2. // 如果 john 消失,secret documents 将会被自动清除
    ```javascript let visitsCountMap = new WeakMap(); // weakmap: user => visits count

// 递增用户来访次数 function countUser(user) { let count = visitsCountMap.get(user) || 0; visitsCountMap.set(user, count + 1); }

let john = { name: “John” }; countUser(john); john = null;// 不久之后,john 离开了

  1. 现在我们不需要去清理 visitsCountMap 了。当 john 对象变成不可访问时,即便它是 WeakMap 里的一个键,它也会连同它作为 WeakMap 里的键所对应的信息一同被从内存中删除。
  2. <a name="LBtzV"></a>
  3. ### [使用案例:缓存](https://zh.javascript.info/weakmap-weakset#shi-yong-an-li-huan-cun)
  4. 当一个函数的结果需要被记住(“缓存”),这样在后续的对同一个对象的调用时,就可以重用这个被缓存的结果。<br />对于多次调用同一个对象,它只需在第一次调用时计算出结果,之后的调用可以直接从 cache 中获取
  5. ```javascript
  6. // 📁 cache.js
  7. let cache = new WeakMap();
  8. // 计算并记结果
  9. function process(obj) {
  10. if (!cache.has(obj)) {
  11. let result = /* calculate the result for */ obj;
  12. cache.set(obj, result);
  13. }
  14. return cache.get(obj);
  15. }
  16. // 📁 main.js
  17. let obj = {/* some object */};
  18. let result1 = process(obj);
  19. let result2 = process(obj);
  20. // ……稍后,我们不再需要这个对象时:
  21. obj = null;
  22. // 无法获取 cache.size,因为它是一个 WeakMap,
  23. // 要么是 0,或即将变为 0
  24. // 当 obj 被垃圾回收,缓存的数据也会被清除

WeakSet

WeakSet 的表现类似:

  • 与 Set 类似,但是我们只能向 WeakSet 添加对象(而不能是原始值)。
  • 对象只有在其它某个(些)地方能被访问的时候,才能留在 set 中。
  • 跟 Set 一样,WeakSet 支持 add,has 和 delete 方法,但不支持 size 和 keys(),并且不可迭代。

它也可以作为额外的存储空间。但并非针对任意数据,而是针对“是/否”的事实。

  1. let visitedSet = new WeakSet();
  2. let john = { name: "John" };
  3. let pete = { name: "Pete" };
  4. let mary = { name: "Mary" };
  5. visitedSet.add(john); // John 访问了我们
  6. visitedSet.add(pete); // 然后是 Pete
  7. visitedSet.add(john); // John 再次访问
  8. // visitedSet 现在有两个用户了
  9. // 检查 John 是否来访过?
  10. alert(visitedSet.has(john)); // true
  11. // 检查 Mary 是否来访过?
  12. alert(visitedSet.has(mary)); // false
  13. john = null;
  14. // visitedSet 将被自动清理

总结

WeakMap 和 WeakSet 最明显的局限性就是不能迭代,并且无法获取所有当前内容。那样可能会造成不便,但是并不会阻止 WeakMap/WeakSet 完成其主要工作 — 成为在其它地方管理/存储“额外”的对象数据。