When learning the cache function, the method of WeakMap caching is finally mentioned (caching the object with input parameter type, and facilitating browser garbage collection when the object is not referenced in the key of WeakMap).

If our parameter were an object (rather than a string, like it is above), We could use WeakMap instead of Map in modern browsers. The benefit of WeakMap is that it would automatically “clean up”. the entries when our object key is no longer accessible.

And since JavaScript already has a Map type data structure, why does it have a data structure called WeakMap type? What does it have to do with garbage collection?

WeakMap has been encountered for a long time, but it has not been systematically studied. Today, I will take a look at it.

  • I met WeakMap
    • Why is WeakMap key weakly referenced?
    • WeakMap is the biggest difference from Map
  • Why is WeakMap type added?
  • Example method of WeakMap
  • The simplest use of WeakMap
  • WeakMap stores private data
  • WeakMap class with clear mode
  • WeakMap type automatic garbage collection cache function
  • The resources

I met WeakMap

  • A WeakMap object is a collection of key-value pairs, where key is weakly referenced
  • The key of WeakMap must be the object type, and the value can be any type

Why is WeakMap key weakly referenced?

What weak references mean: If the object that is the key is not referenced anywhere, the garbage collector (GC) marks it as a target and garbage collects it.

What types of key and value can WeakMap be

Key: must be any object type (object, array, Map, WeakMap, etc.) Value: any (any type, so include undefined, null)

WeakMap is the biggest difference from Map

WeakMap’s key is not enumerable, while Map is enumerable. Non-enumerable means that the key list of WeakMap is not available.

The reason why it is not enumerable is that if you enumerate the key of a WeakMap, you need to rely on the state of the garbage collector (GC), introducing uncertainty.

Why is WeakMap type added?

The MAP API can be implemented in JS by sharing two arrays of four apis (GET, set, HAS, delete) : one for key and one for value. Set elements on the map to push a key and a value synchronously to the end of the array. As a result, the key and value indexes are bound to the two arrays. To get a value from a map, it iterates through all keys to find a match, and then uses that matched index to retrieve the value from the values array.

There are two major drawbacks to this implementation:

  1. The first is that the time complexity of set and search is O(n), where n is the number of key arrays in the map, because both need to traverse the list to find the desired value
  2. The second is memory leaks, because arrays need to ensure that every key and every value is referenced indefinitely. These references prevent the key from being garbage collected, even if the object is not referenced anywhere else.

In contrast, the native WeakMap keeps a “weak” reference to the key. The native WeakMap does not prevent garbage collection and eventually removes references to key objects. A “weak” reference also makes a value good for garbage collection. WeakMap is especially suitable for scenarios where key mapping information is valuable only if it is not garbage collected. In other words, WeakMap is suitable for dynamic garbage collected key scenarios.

Because the reference is weak, the key of a WeakMap is not enumerable. There is no way to get the list of keys. If you enumerate the key of a WeakMap, you need to rely on the state of the garbage collector (GC), introducing uncertainty. If you must have a key, you should use a Map.

Basic concept of WeakMap

grammar

new WeakMap(a)new WeakMap(iterable)
Copy the code

Iterable is an array or any object that can be iterated over, requiring key-value pairs (typically a two-dimensional array). Null will be treated as undefined.

Iterable is a two-dimensional array
const iterable = [
    [{foo:1}, 1], 
    [[1.2.3].2], 
    [window.3]]const iwm = new WeakMap(iterable)
/ / WeakMap {{... } => 1, Window => 3, Array(3) => 2}
Copy the code

Instance methods

WeakMap.prototype.delete(key)

Deletes any value associated with the key. Weakmap.prototype. has(key) returns false after deletion.

WeakMap.prototype.get(key)

Returns the value associated with key, or undefined if no association exists.

WeakMap.prototype.has(key)

Returns the result of whether key exists on WeakMap.

WeakMap.prototype.set(key, value)

Set the specified value for the corresponding key on the WeakMap object. And return the WeakMap object

The simplest use of WeakMap

const wm1 = new WeakMap(),
      wm2 = new WeakMap(),
      wm3 = new WeakMap(a);const o1 = {},
      o2 = function() {},
      o3 = window;

wm1.set(o1, 37);
wm1.set(o2, 'azerty');
wm2.set(o1, o2); // WeakMap values can be any type, including Object and function
wm2.set(o3, undefined);
wm2.set(wm1, wm2); // Key and value can be any object. Including WeakMap!

wm1.get(o2); // "azerty"
wm2.get(o2); // undefined, wm2 has no key o2
wm2.get(o3); // undefined because this is the value set

wm1.has(o2); // true
wm2.has(o2); // false
wm2.has(o3); // true (even if value is undefined)

wm3.set(o1, 37);
wm3.get(o1); / / 37

wm1.has(o1); // true
wm1.delete(o1);
wm1.has(o1); // false
Copy the code

WeakMap stores private data

The data and methods on the instance and prototype chains are public, so implementation details can be hidden through private variables of the WeakMap type.

const privates = new WeakMap(a);function Public() {
  const me = {
    // Private data goes here
  };
  privates.set(this, me);
}

Public.prototype.method = function () {
  const me = privates.get(this);
  // Do stuff with private data in `me`...
};

module.exports = Public;
Copy the code

WeakMap class with clear mode

class ClearableWeakMap {
  constructor(init) {
    this._wm = new WeakMap(init);
  }
  clear() {
    this._wm = new WeakMap(a); }delete(k) {
    return this._wm.delete(k);
  }
  get(k) {
    return this._wm.get(k);
  }
  has(k) {
    return this._wm.has(k);
  }
  set(k, v) {
    this._wm.set(k, v);
    return this; }}Copy the code
const key1 = {foo:1};
const key2 = [1.2.3];
const key3 = window;
const cwm = new ClearableWeakMap([
    [key1, 1], 
    [key2, 2], 
    [key3, 3]
])
cwm.has(key1) // true
console.log(cwm);// ClearableWeakMap {_wm: WeakMap {Window => 3, {... } => 1, Array(3) => 2}}
cwm.clear(); // Garbage collects the current WeakMap and declares the new empty WeakMap
cwm.has(key1) // false
console.log(cwm);// ClearableWeakMap {_wm: WeakMap {}}
Copy the code

WeakMap type automatic garbage collection cache function

There are many ways to implement caching functions, such as single cache, Map full cache, LRU least recent cache, and so on. So why is WeakMap type cache function still needed? This is because the input parameter is cached for object type and is easy for browser garbage collection.

Cache function implementation

function memoizeWeakMap(fn) {
  const wm = new WeakMap(a);return function (arg) {
    if (wm.has(arg)) {
      return wm.get(arg);
    }
    const cachedArg = arg;
    const cachedResult = fn(arg);
    wm.set(cachedArg, cachedResult)
    console.log('weakmap object', wm)
    return cachedResult;
  };
}

let testFn = (bar) = > {return Object.prototype.toString.call(bar)}; // Return the type of the object passed in

let memoizeWeakMapFn = memoizeWeakMap(testFn);

memoizeWeakMapFn(document) // WeakMap generates cache for Document
memoizeWeakMapFn([1.2.3]) Weakmap generates cache for [1,2,3]
memoizeWeakMapFn(function(){}) // WeakMap generates cache for function(){}

memoizeWeakMapFn(new WeakMap())  // WeakMap generates cache for WeakMap instances
memoizeWeakMapFn(new Map()) Weakmap generates cache for Map instances
memoizeWeakMapFn(new Set())  // WeakMap generates cache for Set instances

WeakMap:0: {Array(3) = >"[object Array]"}
1: {function(){} = >"[object Function]"}
2: {WeakMap= > "[object WeakMap]"}
3: {Map(0) = >"[object Map]"}
4: {#document= >"[object HTMLDocument]"}
5: {Set(0) = >"[object Set]"}
Copy the code

How to reflect the garbage collection characteristics of WeakMap

// Ignore some code as above
setTimeout(() = >{
    memoizeWeakMapFn(document)},5000)
Copy the code

Sometimes the last weakMap printing result is as follows:

WeakMap:0: {#document= >"[object HTMLDocument]"}
Copy the code
Why “sometimes”?

Because garbage collection may not be completed when printing, although this will bring uncertainty, it is certain that the key in WeakMap will be garbage collected automatically by the browser, assuming that the object is no longer referenced.

Why is document only saved in WeakMap?

This is because [1,2,3], function(){},new WeakMap(),new Map(), and new Set() are not referenced later, and because they are used as WeakMap keys, they are automatically garbage collected by the browser.

How do YOU keep your key from being garbage collected?

Keep a variable reference to it.

let memoizeWeakMapFn = memoizeWeakMap(testFn);
let retainArray = [1.2.3]; // Keep references from being garbage collected
let retainMap = new Map(a);// Keep references from being garbage collected

memoizeWeakMapFn(document) // WeakMap generates cache for Document
memoizeWeakMapFn(retainArray) Weakmap generates cache for [1,2,3]
memoizeWeakMapFn(function(){}) // WeakMap generates cache for function(){}

memoizeWeakMapFn(new WeakMap())  // WeakMap generates cache for WeakMap instances
memoizeWeakMapFn(retainMap) Weakmap generates cache for Map instances
memoizeWeakMapFn(new Set())  // WeakMap generates cache for Set instances

setTimeout(() = >{
    memoizeWeakMapFn(document)},5000)

Copy the code

The output is as follows:

WeakMap:0: {#document= >"[object HTMLDocument]"}
1: {Map(0) = >"[object Map]"}
2: {Array(3) = >"[object Array]"}
Copy the code

This is because [1,2,3], new Map() is continually referenced by the variables retainArray and retainMap, so it is not garbage collected. Function (){},new WeakMap(), and new Set() are not referenced any more, and since they are used as WeakMap keys, they are automatically garbage collected by the browser.

What if garbage collection is triggered manually?

Chrome DevTools’ Memory panel tool has a button to manually trigger garbage collection.

// ...
setTimeout(() = >{
    memoizeWeakMapFn(document)},5000)
Copy the code

For example, in the above example, a 5-second delay is set: As long as the “garbage collection button” is manually triggered within 5 seconds after the code runs, it can be very accurate to see that the Key of WeakMap is garbage collected.

Of course, the time of 5 seconds can be manually adjusted to ensure that they can trigger garbage collection of WeakMap before the code within setTimeout runs, which can be appropriately increased.

References:

Developer.mozilla.org/en-US/docs/…

Developer.mozilla.org/en-US/docs/…

Fitzgeraldnick.com/2014/01/13/…

whatthefuck.is/memoization

Github.com/reactjs/rea…

Looking forward to communicating with you and making progress together:

  • excellent_developers
  • Front end q&A planet: t.zsxq.com/yBA2Biq