Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

In general, references to objects are strongly referential in JavaScript, meaning that as long as there is a reference to an object, it will not be garbage collected.

const ref = { x: 12, y: 15 };
Copy the code

When it comes to weak references in JavaScript, we all think of WeakMaps and WeakSets, which are the only way to use weak references in JavaScript. Adding an object as a key to a WeakMap or WeakSet does not prevent those objects from being garbage collected.

const wm = new WeakMap(a); {const ref = {};
  const metaData = 'foo';
  wm.set(ref, metaData);
  wm.get(ref);
  / / to the metaData
}
Copy the code

Worth noting: Weakmap.prototype. set(ref, metaData) can be regarded as adding metaData to object ref, so that is why WeakMap key is required to be object, so as long as you reference the object, You can get metaData. However, once there is no more reference to the object, even if the object is used as a key reference of WeakMap, the object will be garbage collected. Corresponding to WeakSet, WeakSet can be regarded as a special case of WeakMap, where all values are Boolean values.

JavaScript WeakMap is not really weak, as long as the key object exists, it is actually a strong reference to its content. Once the key is garbage collected, WeakMap will weaken its contents. A more accurate name for this relationship is ephemeron.

WeakRef is a more advanced API that provides actual weak references, allowing people to see the life cycle of an object. Let’s walk through an example.

For this example, suppose we are developing a chat network application that uses sockets to communicate with the server. Imagine a MovingAvg class that, for performance diagnostics purposes, preserves a set of events from the network socket socket in order to calculate the delayed MovingAvg(simple moving average).

class MovingAvg {
  constructor(socket) {
    this.events = [];
    this.socket = socket;
    this.listener = (ev) = > { this.events.push(ev); };
    socket.addEventListener('message'.this.listener);
  }

  compute(n) {
    // Compute the simple moving average for the last n events.
    / /...}}Copy the code

A MovingAvgComponent class controls when to start and stop monitoring delayed MovingAvgComponent.

class MovingAvgComponent {
  constructor(socket) {
    this.socket = socket;
  }

  start() {
    this.movingAvg = new MovingAvg(this.socket);
  }

  stop() {
    // Allow the garbage collector to reclaim memory.
    this.movingAvg = null;
  }

  render() {
    // Do rendering.
    / /...}}Copy the code

We know that keeping all server information in one MovingAvg instance consumes a lot of memory, so when stopping monitoring, we need to empty this. MovingAvg so that garbage collection can reclaim the memory used to store server information.

A check in the DevTools memory panel reveals that memory has not been reclaimed at all! Why is that? Experienced Web developers have probably noticed that the event listener is a strong reference and must be explicitly removed.

Let’s use a diagram for calling relationships between objects. After a call to start(), the diagram between objects is shown below, where a solid arrow means a strong reference. All objects reachable via the solid arrow from the MovingAvgComponent instance are ungarbageable.

After stop(), the strong reference to the MovingAvg instance is removed from the MovingAvgComponent instance, but there is no empties listener through the socket, so the classes below refer to each other and MovingAvg is not collected in the garbage collection.

Thus, the listener in the MovingAvg instance, by referring to this, keeps the entire instance from participating in garbage collection as long as the event listener is not deleted. So far, the solution has been to manually unregister event listeners through a handler.

class MovingAvg {
  constructor(socket) {
    this.events = [];
    this.socket = socket;
    this.listener = (ev) = > { this.events.push(ev); };
    socket.addEventListener('message'.this.listener);
  }

  dispose() {
    this.socket.removeEventListener('message'.this.listener);
  }

  / /...
}
Copy the code

Weak references and Finalizers