When does WeakMap work?

First of all, we must understand that even without WeakMap, the world of JS is also running as usual, WeakMap in the JS world, and its name is the same - “weak”, weak.

Belong to the kind of role, but not in the key position to do great things, somewhat similar to some CSS function selector, although simpler and more convenient to use, but usually use JS to achieve a pretty good interactive effect, so it is not so popular and popular.

WeakMap is the role of more effective garbage collection, release memory.

We usually develop Web applications are so simple, even if the code is very garbage, eat a lot of memory, so what, the page is still smooth, the user as usual no perception.

So, for most developers and applications, the business value of WeakMap is very low.

So it seems that WeakMap is nothing to learn.

But it’s not.

If it is a mega-application, or a product with a large user base, or a server with a high load scenario, the requirements for memory management are very high, and the advantages of WeakMap can be reflected at this time.

This is actually a bit of a paradox in the flavor.

All the above scenarios that need to use WeakMap must be scenarios that require senior front-end development, and if you don’t even know WeakMap, you can’t talk about seniority.

That is, you learn WeakMap and similar JS knowledge points, only to have the opportunity to participate in the project must use these JS features, to complete the proof of self-worth and contribution.

So, there is no knowledge is useless, just a matter of timing, the so-called thick and thin, is such a meaning.

Garbage? Memory?

As far as memory management is concerned, JS developers can be grouped into the following categories.

  1. memory? What memory? I can write whatever I want in JS, and it runs just fine! Anyway, the browser page is closed and nothing is left. 2.
  2. well, after this object is useless, I can set it to null. awareness is good, set also set, but whether the memory is released or not is unknown, see the luck. 3.
  3. here set null can not, because the object is also referenced in other places, other references to the place to delete, belong to a deeper understanding of the JS basic skills are very solid.

To give an example that is easier for everyone to understand, the page is known to have a DOM element, the HTML structure is as follows.

1
<img id="img" src="https://bookcover.yuewen.com/qdbimg/349573/1027030348/180">

Then, this DOM element needs to be deleted, and this is how Xiaoming handles it.

1
2
var eleImage = document.getElementById('img');
eleImage.remove();

Is there anything wrong with this treatment?

In terms of effect, it completely solves the need.

But in fact, although the image element in the page is deleted, the DOM object in memory still exists.

Let’s test it like this.

1
2
3
4
5
6
var eleImage = document.getElementById('img');
eleImage.remove();

setTimeout(() => {
  document.body.append(eleImage);
}, 2000);

You will see that the image is deleted (as in the following GIF recording), and then it appears on the page again after 2 seconds, because the eleImage is still in memory, not cleared, and will not be recycled.

So, if you determine that the eleImage is no longer needed, you need to execute one more sentence while setting it to null.

1
2
3
var eleImage = document.getElementById('img');
eleImage.remove();
eleImage = null;

This way, when JS performs garbage collection, it will take back the garbage that is eleImage and release the memory.

You can see if you are the type of JS developer who doesn’t care about memory, or the type of JS developer who pays attention to freeing up unwanted memory.

OK, it’s not over yet. Sometimes, setting eleImage to null doesn’t really reclaim memory.

Practical development, for example, sometimes requires remembering the initial outerHTML characters for easy restoration. In order to associate with the original DOM, some developments manage DOM elements and outerHTML strings in the same array:.

1
2
3
4
5
6
var eleImage = document.getElementById('img');
var storage = {
    arrDom: [eleImage, eleImage.outerHTML]
};
eleImage.remove();
eleImage = null;

At this point, the eleImage DOM object is still in memory. Because eleImage is referenced by storage.arrDom, even if eleImage is set to null, the memory cannot be completely freed.

Test.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var eleImage = document.getElementById('img');
var storage = {
    arrDom: [eleImage, eleImage.outerHTML]
};
eleImage.remove();
eleImage = null;

setTimeout(() => {
  document.body.append(storage.arrDom[0]);
}, 2000);

Again, you can see that the image was deleted and then reappeared 2 seconds later.

At this point, to completely free the memory of the image, the following line still needs to be executed.

1
storage.arrDom[0] = null;

Imagine if you could free up memory to do this? If you can, then you can be categorized as a senior JS front-end developer.

However, it is too tiring to manually identify which areas of memory to release purely by technical means. Even if people who know how to do this sometimes don’t bother to manage it, is there any means by which I can just set the variable to null and have all the memory in the referenced places automatically released?

This can be considered using WeakMap.

Usage Scenarios

What would the above example look like if implemented back using WeakMap?

Code.

1
2
3
4
5
var eleImage = document.getElementById('img');
var storeMap = new WeakMap();
storeMap.set(eleImage, eleImage.outerHTML);
eleImage.remove();
eleImage = null;

The same is true for caching the outerHTML data of the image, but here a WeakMap object is used, with eleImage as a weak key, so that once eleImage is set to null, all related data will be freed.

Whether the memory is freed or not, the above example is not good to test, one is to use tools, and the other is that the memory change is too small to see.

However, we can use Map object to compare, if it is a Map object, eleImage as a key, that is a strong reference, is always in memory, for example.

1
2
3
4
5
6
7
8
9
var eleImage = document.getElementById('img');
var storeMap = new Map();
storeMap.set(eleImage, eleImage.outerHTML);
eleImage.remove();
eleImage = null;

setTimeout(() => {
  document.body.append(storeMap.keys().next().value);
}, 2000);

As you can see, although the eleImage is removed and set to null, it is still in memory and can be appended to the page.

Based on the above analysis and description, we can draw the following conclusion.

Summary

When we need to temporarily store data on an object, use WeakMap, especially for developers who don’t have a deep understanding of JS, because it saves them from worrying about the “memory leak” problem that is often talked about.

Because when the time comes to delete the object, all the relevant references and associated memory will be released.

That is, although I can not understand how the code is executed, but I write so that the performance is good, the style is high!

Back to the WeakMap syntax itself

Having said that, it’s time to introduce the WeakMap syntax.

1
let myWm = new WeakMap()

At this point, myWm is a new WeakMap object that includes the following methods.

1
2
3
4
5
6
7
8
// delete key
myWm.delete(key);
// set key & value
myWm.set(key, value);
// Does it contain a key
myWm.has(key);
// Get the value corresponding to the key
myWm.get(key);

Description

  • key can only be an object, not a primitive data type (string, number, true or false, null, undefined, symbol, etc.)
  • keys in WeakMap are not enumerable

The key can only be an object

The following uses are all possible.

1
2
3
4
myWm.set([], 1);
myWm.set(new Date(), '鑫空间');
myWm.set(()=>{}, 1);
myWm.set(document.createElement('by-zhangxinxu'), 1);

But if the key is not an object, but a basic type like a string, it will report an error, e.g.

1
2
3
// An exception will be thrown
// “TypeError: Invalid value used as weak map key
myWm.set('css新世界', true);

Therefore, WeakMap is suitable for scenarios where data is temporarily cached on objects.

key cannot be enumerated

Unlike Map objects, Map objects can be enumerated and have keys(), values(), entries() methods, and can be traversed using forEach.

But WeakMap cannot be enumerated, and this feature of WeakMap can also be used to simulate private properties.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
const myWm = new WeakMap();
class Fish {
    constructor(name) {
        myWm.set(this, { 
            _fishbone: ['草鱼', '鲫鱼', '青鱼', '鲤鱼', '鲢鱼', '鳙鱼', '鳊鱼', '翘嘴', '餐条'],
        });
        this.name = name;
    }

    isBone() {
        return myWm.get(this)._fishbone.includes(this.name);
    }
}

// 测试,买了两条鱼
let fish1 = new Fish('草鱼');
let fish2 = new Fish('回鱼');

// 返回 true,有刺
console.log(fish1.isBone());
// 返回 false,没有肌间刺
console.log(fish2.isBone());

In the above code, _fishbone is associated with Fish object, but it can’t be obtained directly through Fish object.

If the name is not known, it cannot be traversed by myWm.

For more detailed syntax and illustration of WeakMap, please refer to MDN document: MDN WeakMap


Reference https://www.zhangxinxu.com/wordpress/2021/08/js-weakmap-es6/