This is the 16th day of my participation in the August Text Challenge.More challenges in August

A, the Set

1. Basic usage

ES6 provides a new data structure, Set. It is similar to an array, but the values of the members are unique and there are no duplicate values.

The Set itself is a constructor used to generate the Set data structure.

const s = new Set(a); [2.3.5.4.5.2.2].forEach(x= > s.add(x));

for (let i of s) {
  console.log(i);
}
// 2 3 5 4
Copy the code

The code above adds members to the Set structure using the add() method, which shows that the Set structure does not add duplicate values.

The Set function can take an array (or some other data structure with an Iterable interface) as an argument for initialization.

/ / a
const set = new Set([1.2.3.4.4]);
[...set]
// [1, 2, 3, 4]

/ / two cases
const items = new Set([1.2.3.4.5.5.5.5]);
items.size / / 5

/ / 3
const set = new Set(document.querySelectorAll('div'));
set.size / / 56

/ / similar to
const set = new Set(a);document
 .querySelectorAll('div')
 .forEach(div= > set.add(div));
set.size / / 56
Copy the code

In both examples, the Set function takes an array, and example 3 takes an array-like object.

The code above also shows a way to remove duplicate members of an array.

// Remove duplicate members of the array
[...new Set(array)]
Copy the code

The above method can also be used to remove duplicate characters from a string.

[...new Set('ababbc')].join(' ')
// "abc"
Copy the code

When you add a value to a Set, no type conversion occurs, so 5 and “5” are two different values. Inside a Set, the algorithm used to determine whether two values are different is called “same-value-zero equality.” It is similar to the exact equality operator (===), except that when adding a value to a Set, NaN is considered equal to itself, whereas the exact equality operator assumes NaN is not equal to itself.

let set = new Set(a);let a = NaN;
let b = NaN;
set.add(a);
set.add(b);
set // Set {NaN}
Copy the code

The code above adds two nans to the Set instance, but only one. This indicates that within a Set, two Nans are equal.

In addition, two objects are always not equal.

let set = new Set(a); set.add({}); set.size/ / 1

set.add({});
set.size / / 2
Copy the code

The code above says that since two empty objects are not equal, they are treated as two values.

2. Set instance properties and methods

Instances of the Set structure have the following properties.

  • Set.prototype.constructorThe: constructor is the defaultSetFunction.
  • Set.prototype.sizeReturns theSetThe total number of members of an instance.

The methods of a Set instance fall into two broad categories: operation methods (for manipulating data) and traversal methods (for traversing members). The following four operations are introduced.

  • Set.prototype.add(value): Adds a value, returnsSetThe structure itself.
  • Set.prototype.delete(value): Deletes a value and returns a Boolean value indicating whether the deletion was successful.
  • Set.prototype.has(value): Returns a Boolean value indicating whether the value isSetA member of.
  • Set.prototype.clear(): Clears all members with no return value.

Examples of these properties and methods are shown below.

s.add(1).add(2).add(2);
// Notice that 2 is added twice

s.size / / 2

s.has(1) // true
s.has(2) // true
s.has(3) // false

s.delete(2);
s.has(2) // false
Copy the code

Here’s a comparison to see how Object and Set structures are written differently to determine whether or not to include a key.

// How to write objects
const properties = {
  'width': 1.'height': 1
};

if (properties[someName]) {
  // do something
}

// Set
const properties = new Set(a); properties.add('width');
properties.add('height');

if (properties.has(someName)) {
  // do something
}
Copy the code

The array. from method converts a Set structure to an Array.

const items = new Set([1.2.3.4.5]);
const array = Array.from(items);
Copy the code

This provides another way to remove duplicate members of an array.

function dedupe(array) {
  return Array.from(new Set(array));
}

dedupe([1.1.2.3]) / / [1, 2, 3]
Copy the code

3. Traversal operations

An instance of a Set structure has four traversal methods that can be used to traverse members.

  • Set.prototype.keys(): returns a traverser for key names
  • Set.prototype.values(): returns a traverser for key values
  • Set.prototype.entries(): returns a traverser for key-value pairs
  • Set.prototype.forEach(): Iterates through each member using the callback function

In particular, the traversal order of a Set is the insertion order. This feature can be useful, such as using Set to hold a list of callback functions that are guaranteed to be called in the order they were added.

(1) keys(), values(), entries()

The keys, values, and entries methods all return Iterator objects (see the Chapter on Iterator Objects). Because the Set structure has no key name, only the key value (or the key name and the key value are the same value), the keys and values methods behave exactly the same.

let set = new Set(['red'.'green'.'blue']);

for (let item of set.keys()) {
  console.log(item);
}
// red
// green
// blue

for (let item of set.values()) {
  console.log(item);
}
// red
// green
// blue

for (let item of set.entries()) {
  console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
Copy the code

In the code above, the entries method returns a traverser that contains both the key name and the key value, so it outputs an array of identical members one at a time.

An instance of a Set structure is traversable by default, and its default traverser generator is its values method.

Set.prototype[Symbol.iterator] === Set.prototype.values
// true
Copy the code

This means that you can omit the values method and use for… Of loops through Set.

let set = new Set(['red'.'green'.'blue']);

for (let x of set) {
  console.log(x);
}
// red
// green
// blue
Copy the code

(2) the forEach ()

An instance of a Set structure, like an array, has a forEach method that performs some operation on each member and returns no value.

let set = new Set([1.4.9]);
set.forEach((value, key) = > console.log(key + ':' + value))
/ / 1:1
/ / 4:4
/ / 9:9
Copy the code

The above code shows that the argument to the forEach method is a handler function. The arguments to this function are the same as the forEach of the array: the key value, the key name, and the collection itself (omitted in the previous example). Note that the key name of the Set structure is the key value (both are the same value), so the value of the first argument is always the same as the value of the second argument.

Alternatively, the forEach method can have a second argument that represents the this object inside the binding handler.

(3) Application of traversal

Extended operators (…) Internally use for… Of loop, so it can also be used for Set structures.

let set = new Set(['red'.'green'.'blue']);
let arr = [...set];
// ['red', 'green', 'blue']
Copy the code

The extension operator, combined with the Set structure, removes duplicate members of an array.

let arr = [3.5.2.2.5.5];
let unique = [...new Set(arr)];
/ / [3, 5, 2]
Copy the code

Furthermore, the map and filter methods of arrays can also be used indirectly with sets.

let set = new Set([1.2.3]);
set = new Set([...set].map(x= > x * 2));
{2, 4, 6}

let set = new Set([1.2.3.4.5]);
set = new Set([...set].filter(x= > (x % 2) = =0));
Set {2, 4}
Copy the code

Therefore, it is easy to implement Union, intersection, and Difference using Set.

let a = new Set([1.2.3]);
let b = new Set([4.3.2]);

/ / and set
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}

/ / intersection
let intersect = new Set([...a].filter(x= > b.has(x)));
// set {2, 3}

// The difference between a and b
let difference = new Set([...a].filter(x= >! b.has(x)));// Set {1}
Copy the code

If you want to change the Set structure synchronously during traversal, there is no direct way, but there are two workarounds. One is to use the original Set structure to map a new structure, and then assign a value to the original Set structure; The other is to use the array. from method.

/ / method
let set = new Set([1.2.3]);
set = new Set([...set].map(val= > val * 2));
// Set values are 2, 4, 6

/ / method 2
let set = new Set([1.2.3]);
set = new Set(Array.from(set, val= > val * 2));
// Set values are 2, 4, 6
Copy the code

The code above provides two ways to change the original Set structure directly in the traversal operation.

Second, the WeakSet

1. The meaning of

WeakSet structure is similar to Set, which is also a collection of non-repeating values. However, it differs from Set in two ways.

First, WeakSet members can only be objects, not other types of values.

const ws = new WeakSet(a); ws.add(1)
// TypeError: Invalid value used in weak set
ws.add(Symbol())
// TypeError: invalid value used in weak set
Copy the code

The above code attempts to add a value and a Symbol value to WeakSet, and returns an error because WeakSet can only place objects.

Secondly, the objects in WeakSet are weak references, that is, garbage collection mechanism does not consider WeakSet’s reference to the object, that is to say, if other objects no longer reference the object, then garbage collection mechanism will automatically recover the memory occupied by the object, not considering the object still exists in WeakSet.

This is because the garbage collection mechanism relies on reference counting and does not free the memory if the number of references to a value is not zero. After you finish using the value, you sometimes forget to dereference it, causing memory to be unable to be freed, which can lead to a memory leak. WeakSet references are not included in the garbage collection mechanism, so there is no problem. Therefore, WeakSet is suitable for temporarily storing a group of objects and information bound to the object. As soon as the object disappears externally, its reference in WeakSet disappears automatically.

Due to the above feature, a WeakSet member is not suitable for reference because it will disappear at any time. In addition, because the number of internal WeakSet members depends on whether garbage collection mechanism is running, the number of members may be different before and after operation, and when garbage collection mechanism is running is unpredictable, so ES6 stipulates that WeakSet cannot be traversed.

These characteristics also apply to the WeakMap structure described later in this chapter.

2. Grammar

WeakSet is a constructor that creates a WeakSet data structure using the new command.

const ws = new WeakSet(a);Copy the code

As a constructor, WeakSet can take an array or array-like object as a parameter. (In fact, any object with an Iterable interface can be used as a WeakSet argument.) All members of the array automatically become members of a WeakSet instance object.

const a = [[1.2], [3.4]].const ws = new WeakSet(a);
// WeakSet {[1, 2], [3, 4]}
Copy the code

In the code above, A is an array, and it has two members, both of which are arrays. If A is used as a parameter in the WeakSet constructor, members of A will automatically become members of WeakSet.

Note that it is the members of the ARRAY A that become WeakSet members, not the array A itself. This means that the members of an array can only be objects.

const b = [3.4];
const ws = new WeakSet(b);
// Uncaught TypeError: Invalid value used in weak set(...)
Copy the code

In the above code, the member of array B is not an object, and an error will be reported when adding WeakSet.

WeakSet structure has the following three methods.

  • WeakSet.prototype.add(value)To:WeakSetExample Adds a new member.
  • WeakSet.prototype.delete(value): removeWeakSetInstance of the specified member.
  • WeakSet.prototype.has(value): Returns a Boolean value indicating whether a value is inWeakSetIn an instance.

Here’s an example.

const ws = new WeakSet(a);const obj = {};
const foo = {};

ws.add(window);
ws.add(obj);

ws.has(window); // true
ws.has(foo);    // false

ws.delete(window);
ws.has(window);    // false
Copy the code

WeakSet has no size property, so there is no way to traverse its members.

ws.size // undefined
ws.forEach // undefined

ws.forEach(function(item){ console.log('WeakSet has ' + item)})
// TypeError: undefined is not a function
Copy the code

The above code attempts to retrieve the size and forEach attributes, but fails.

WeakSet cannot traverse, because members are weak references and may disappear at any time. The traversal mechanism cannot guarantee the existence of members. It is likely that members cannot be retrieved just after the traversal is completed. WeakSet is useful for storing DOM nodes without worrying about memory leaks when they are removed from documents.

Here is another example of WeakSet.

const foos = new WeakSet(a)class Foo {
  constructor() {
    foos.add(this)
  }
  method () {
    if(! foos.has(this)) {
      throw new TypeError('foo.prototype. method can only be called on instances of Foo! '); }}}Copy the code

The above code ensures that instance methods of Foo can only be called on instances of Foo. The advantage of using WeakSet here is that the reference to the instance by foos is not counted in the memory reclamation mechanism, so when deleting the instance, you do not need to consider foos and there is no memory leak.

Third, the Map

1. Meaning and basic usage

JavaScript objects are essentially collections of key-value pairs (Hash structures), but traditionally strings can only be used as keys. This puts a great limit on its use.

const data = {};
const element = document.getElementById('myDiv');

data[element] = 'metadata';
data['[object HTMLDivElement]'] // "metadata"
Copy the code

The above code is intended to use a DOM node as the key of the data object, but since objects only accept strings as key names, element is automatically converted to a string.

To solve this problem, ES6 provides Map data structures. It is a collection of key-value pairs similar to objects, but the range of “keys” is not limited to strings. Values of all types (including objects) can be used as keys. In other words, the Object structure provides string-value mapping, and the Map structure provides value-value mapping, which is a more complete Hash structure implementation. If you need key-value data structures, Map is better than Object.

const m = new Map(a);const o = {p: 'Hello World'};

m.set(o, 'content')
m.get(o) // "content"

m.has(o) // true
m.delete(o) // true
m.has(o) // false
Copy the code

The above code uses the set method of the Map structure to treat the object O as a key of M, then reads the key using the get method, and then deletes the key using the DELETE method.

The example above shows how to add members to a Map. As a constructor, a Map can also take an array as an argument. The members of this array are arrays representing key-value pairs.

const map = new Map([['name'.'Joe'],
  ['title'.'Author']]); map.size/ / 2
map.has('name') // true
map.get('name') // "/"
map.has('title') // true
map.get('title') // "Author"
Copy the code

The code above specifies two keys name and title when creating a Map instance.

The Map constructor takes an array as an argument and actually performs the following algorithm.

const items = [
  ['name'.'Joe'],
  ['title'.'Author']].const map = new Map(a); items.forEach(([key, value]) = > map.set(key, value)
);
Copy the code

In fact, not just arrays, but any data structure with an Iterator interface where each member is a two-element array (see chapter Iterator) can be taken as a parameter to the Map constructor. That is, both sets and maps can be used to generate new maps.

const set = new Set([['foo'.1],
  ['bar'.2]]);const m1 = new Map(set);
m1.get('foo') / / 1

const m2 = new Map([['baz'.3]]);
const m3 = new Map(m2);
m3.get('baz') / / 3
Copy the code

In the code above, we use a Set object and a Map object, respectively, as arguments to the Map constructor, resulting in a new Map object.

If the same key is assigned more than once, the subsequent value overrides the previous value.

const map = new Map(a); map .set(1.'aaa')
.set(1.'bbb');

map.get(1) // "bbb"
Copy the code

The code above assigns key 1 twice in a row, overwriting the previous value.

Returns undefined if an unknown key is read.

new Map().get('asfddfsasadf')
// undefined
Copy the code

Note that only references to the same object are treated as the same key by the Map structure. Be very careful about this.

const map = new Map(a); map.set(['a'].555);
map.get(['a']) // undefined
Copy the code

The set and GET methods in the above code appear to be for the same key, but in fact they are two different array instances, the memory address is different, so the get method cannot read the key, return undefined.

Similarly, two instances of the same value are treated as two keys in the Map structure.

const map = new Map(a);const k1 = ['a'];
const k2 = ['a'];

map
.set(k1, 111)
.set(k2, 222);

map.get(k1) / / 111
map.get(k2) / / 222
Copy the code

In the code above, the values of variables K1 and k2 are the same, but they are treated as two keys in the Map structure.

Map keys are actually bound to memory addresses. As long as the memory addresses are different, they are regarded as two keys. This solves the clash problem, so when we extend someone else’s library, we don’t have to worry about our properties having the same name as the original author’s properties if we use objects as keys.

If the Map’s key is a value of a simple type (number, string, Boolean), the Map will treat the two values as one key as long as they are strictly equal, such as 0 and -0 being one key, and true and the string true being two different keys. Also, undefined and NULL are two different keys. Although NaN is not strictly equal to itself, Map treats it as the same key.

let map = new Map(a); map.set(-0.123);
map.get(+0) / / 123

map.set(true.1);
map.set('true'.2);
map.get(true) / / 1

map.set(undefined.3);
map.set(null.4);
map.get(undefined) / / 3

map.set(NaN.123);
map.get(NaN) / / 123
Copy the code

2. Instance properties and operation methods

An instance of a Map structure has the following properties and operations.

(1) Size attribute

The size property returns the total number of Map structure members.

const map = new Map(a); map.set('foo'.true);
map.set('bar'.false);

map.size / / 2
Copy the code

Prototype set(key, value)

The set method sets the key corresponding to the key name key to value and returns the entire Map structure. If the key already has a value, the key value is updated, otherwise the key is generated.

const m = new Map(a); m.set('edition'.6)        // keys are strings
m.set(262.'standard')     // Keys are numeric values
m.set(undefined.'nah')    / / key is undefined
Copy the code

The set method returns the current Map object, so it can be chained.

let map = new Map()
  .set(1.'a')
  .set(2.'b')
  .set(3.'c');
Copy the code

(3) the Map. The prototype. The get (key)

The get method reads the corresponding key, and returns undefined if the key is not found.

const m = new Map(a);const hello = function() {console.log('hello'); }; m.set(hello,'Hello ES6! ') // Keys are functions

m.get(hello)  // Hello ES6!
Copy the code

(4) the Map) prototype) from the (key)

The HAS method returns a Boolean value indicating whether a key is in the current Map object.

const m = new Map(a); m.set('edition'.6);
m.set(262.'standard');
m.set(undefined.'nah');

m.has('edition')     // true
m.has('years')       // false
m.has(262)           // true
m.has(undefined)     // true
Copy the code

(5) the Map. The prototype. The delete (key)

The delete method deletes a key and returns true. If deletion fails, return false.

const m = new Map(a); m.set(undefined.'nah');
m.has(undefined)     // true

m.delete(undefined)
m.has(undefined)       // false
Copy the code

(6) the Map. The prototype. The clear ()

The clear method clears all members with no return value.

let map = new Map(a); map.set('foo'.true);
map.set('bar'.false);

map.size / / 2
map.clear()
map.size / / 0
Copy the code

3. Traversal method

The Map structure natively provides three traverser generating functions and one traversal method.

  • Map.prototype.keys(): returns a traverser for key names.
  • Map.prototype.values(): returns a traverser for key values.
  • Map.prototype.entries(): returns a traverser for all members.
  • Map.prototype.forEach(): traversalMapAll members of the.

Note in particular that the Map traversal order is the insertion order.

const map = new Map([['F'.'no'],
  ['T'.'yes']]);for (let key of map.keys()) {
  console.log(key);
}
// "F"
// "T"

for (let value of map.values()) {
  console.log(value);
}
// "no"
// "yes"

for (let item of map.entries()) {
  console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"

/ / or
for (let [key, value] of map.entries()) {
  console.log(key, value);
}
// "F" "no"
// "T" "yes"

// Equivalent to using map.entries()
for (let [key, value] of map) {
  console.log(key, value);
}
// "F" "no"
// "T" "yes"
Copy the code

The example at the end of the code above, which represents the default traverser interface (symbol. iterator property) of the Map structure, is the Entries method.

map[Symbol.iterator] === map.entries
// true
Copy the code

A faster way to convert a Map structure into an array structure is to use the extension operator (…). .

const map = new Map([[1.'one'],
  [2.'two'],
  [3.'three']]); [...map.keys()]/ / [1, 2, 3]

[...map.values()]
// ['one', 'two', 'three']

[...map.entries()]
// [[1,'one'], [2, 'two'], [3, 'three']]

[...map]
// [[1,'one'], [2, 'two'], [3, 'three']]
Copy the code

Map traversal and filter can be realized by combining the map and filter methods of arrays. (Map does not have map and filter methods.)

const map0 = new Map()
  .set(1.'a')
  .set(2.'b')
  .set(3.'c');

const map1 = new Map(
  [...map0].filter(([k, v]) = > k < 3));{1 => 'a', 2 => 'b'}

const map2 = new Map(
  [...map0].map(([k, v]) = > [k * 2.'_' + v])
    );
{2 => '_A ', 4 =>' _B ', 6 => '_c'}
Copy the code

In addition, maps have a forEach method, similar to the forEach method for arrays, that can also be traversed.

map.forEach(function(value, key, map) {
  console.log("Key: %s, Value: %s", key, value);
});
Copy the code

The forEach method can also take a second argument that binds this.

const reporter = {
  report: function(key, value) {
    console.log("Key: %s, Value: %s", key, value); }}; map.forEach(function(value, key, map) {
  this.report(key, value);
}, reporter);
Copy the code

In the code above, the this of the forEach method’s callback refers to Reporter.

4. Interconversion with other data structures

(1) Map is converted into an array

As mentioned earlier, the most convenient way to turn a Map into an array is to use the extension operator (…). .

const myMap = new Map()
  .set(true.7)
  .set({foo: 3},'abc']);
[...myMap]
// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
Copy the code

(2) The array is converted to Map

The array is converted to a Map by passing it to the Map constructor.

new Map([[true.7],
  [{foo: 3},'abc']]])// Map {
// true => 7,
// Object {foo: 3} => ['abc']
// }
Copy the code

(3) Map is converted into an object

If all Map keys are strings, it can be converted to objects losslessly.

function strMapToObj(strMap) {
  let obj = Object.create(null);
  for (let [k,v] of strMap) {
    obj[k] = v;
  }
  return obj;
}

const myMap = new Map()
  .set('yes'.true)
  .set('no'.false);
strMapToObj(myMap)
// { yes: true, no: false }
Copy the code

If there is a non-string key name, it is converted to a string and used as the object’s key name.

(4) The object is converted to Map

Objects can be turned into maps through Object.entries().

let obj = {"a":1."b":2};
let map = new Map(Object.entries(obj));
Copy the code

Alternatively, you can implement a conversion function yourself.

function objToStrMap(obj) {
  let strMap = new Map(a);for (let k of Object.keys(obj)) {
    strMap.set(k, obj[k]);
  }
  return strMap;
}

objToStrMap({yes: true.no: false})
// Map {"yes" => true, "no" => false}
Copy the code

(5) Map is converted to JSON

There are two different cases of Map conversion to JSON. In one case, Map keys are all strings, and you can choose to convert to object JSON.

function strMapToJson(strMap) {
  return JSON.stringify(strMapToObj(strMap));
}

let myMap = new Map().set('yes'.true).set('no'.false);
strMapToJson(myMap)
// '{"yes":true,"no":false}'
Copy the code

Alternatively, if the Map has a non-string key name, you can choose to convert it to an array JSON.

function mapToArrayJson(map) {
  return JSON.stringify([...map]);
}

let myMap = new Map().set(true.7).set({foo: 3},'abc']);
mapToArrayJson(myMap)
// '[[true,7],[{"foo":3},["abc"]]]'
Copy the code

(6) Convert JSON to Map

JSON to Map. Normally, all key names are strings.

function jsonToStrMap(jsonStr) {
  return objToStrMap(JSON.parse(jsonStr));
}

jsonToStrMap('{"yes": true, "no": false}')
// Map {'yes' => true, 'no' => false}
Copy the code

However, there is a special case where the entire JSON is an array, and each array member itself is an array with two members. In this case, it can be converted into a Map. This is often the inverse of the Map to array JSON.

function jsonToMap(jsonStr) {
  return new Map(JSON.parse(jsonStr));
}

jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}
Copy the code

Four, WeakMap

1. The meaning of

WeakMap structure is similar to Map structure and is also used to generate a set of key-value pairs.

// WeakMap can add members using the set method
const wm1 = new WeakMap(a);const key = {foo: 1};
wm1.set(key, 2);
wm1.get(key) / / 2

WeakMap can also accept an array,
// as arguments to the constructor
const k1 = [1.2.3];
const k2 = [4.5.6];
const wm2 = new WeakMap([[k1, 'foo'], [k2, 'bar']]);
wm2.get(k2) // "bar"
Copy the code

WeakMap differs from Map in two ways.

First, WeakMap only accepts objects as key names (except null) and does not accept other types of values as key names.

const map = new WeakMap(a); map.set(1.2)
// TypeError: 1 is not an object!
map.set(Symbol(), 2)
// TypeError: Invalid value used as weak map key
map.set(null.2)
// TypeError: Invalid value used as weak map key
Copy the code

In the above code, if the value 1 and Symbol value are used as the key name of WeakMap, an error will be reported.

Second, the object to which the key name of WeakMap points is not included in the garbage collection mechanism.

WeakMap is designed so that sometimes we want to put some data on an object, but that creates a reference to that object. Take a look at the following example.

const e1 = document.getElementById('foo');
const e2 = document.getElementById('bar');
const arr = [
  [e1, 'foo elements'],
  [e2, 'the bar elements']];Copy the code

In the above code, E1 and e2 are two objects, and we add some text to these two objects through the ARR array. This forms arR’s reference to E1 and E2.

Once these two objects are no longer needed, we must manually remove the reference, otherwise the garbage collection mechanism will not free the memory occupied by E1 and E2.

// when e1 and E2 are not required
// The reference must be removed manually
arr [0] = null;
arr [1] = null;
Copy the code

So this is obviously an inconvenient way to write it. If you forget to write, you will cause a memory leak.

WeakMap was created to solve this problem, and its key names refer to objects that are weak references, meaning that the garbage collection mechanism does not take that reference into account. Therefore, as long as all other references to the referenced object are cleared, the garbage collection mechanism frees the memory occupied by the object. In other words, once it is no longer needed, the key name object and the corresponding key value pair in WeakMap will disappear automatically, without manually deleting the reference.

Basically, if you want to add data to an object and don’t want to interfere with garbage collection, you can use WeakMap. A typical application scenario is when a WeakMap structure can be used to add data to a DOM element of a web page. When the DOM element is cleared, its corresponding WeakMap record is automatically removed.

const wm = new WeakMap(a);const element = document.getElementById('example');

wm.set(element, 'some information');
wm.get(element) // "some information"
Copy the code

In the above code, create a New Weakmap instance. Then, a DOM node is stored in the instance as the key name, and some additional information is stored in WeakMap as the key value. In this case, a WeakMap reference to an Element is a weak reference and is not counted in the garbage collection mechanism.

That is, the DOM node object above has a reference count of 1, not 2. At this point, once the reference to the node is removed, its memory is freed by the garbage collection mechanism. The key value pair saved by Weakmap will also disappear automatically.

In short, the special occasion of WeakMap is that the object corresponding to its key may disappear in the future. The WeakMap structure helps prevent memory leaks.

Note that WeakMap weakly references only the key name, not the key value. Key values are still normal references.

const wm = new WeakMap(a);let key = {};
let obj = {foo: 1};

wm.set(key, obj);
obj = null;
wm.get(key)
// Object {foo: 1}
Copy the code

In the code above, the key value obj is a normal reference. So, even if obj’s reference is removed outside of WeakMap, the reference inside Of WeakMap still exists.

2. The syntax of WeakMap

WeakMap differs from Map in API mainly in two aspects. First, there is no traversal operation (that is, no keys(), values() and entries() methods) and no size attribute. Because there is no way to list all the key names, the existence of a particular key name is completely unpredictable and depends on whether the garbage collection mechanism is running. One moment the key name is available, the next moment the garbage collection mechanism is suddenly running, and the key name is gone, so in case of uncertainty, there is a general rule that the key name is not available. Second, it cannot be cleared, that is, the clear method is not supported. Therefore, WeakMap has only four methods available: get(), set(), has(), delete().

const wm = new WeakMap(a);// the size, forEach, and clear methods do not exist
wm.size // undefined
wm.forEach // undefined
wm.clear // undefined
Copy the code

3. Example of WeakMap

The WeakMap example is hard to demonstrate because you can’t watch the references inside it disappear automatically. At this point, other references are removed, and there is no reference to the key name pointing to WeakMap, so it is impossible to verify whether the key name exists.

If a reference is pointing to a value that takes up a lot of memory, you can tell by using the Node’s process.memoryUsage method. With this in mind, user VTXF added the following example.

First, open the Node command line.

$ node --expose-gc
Copy the code

In the code above, the — expose-GC parameter indicates that the garbage collection mechanism is allowed to be performed manually.

Then, execute the following code.

// Perform a garbage collection manually to ensure accurate memory usage
> global.gc();
undefined

// Check the initial memory usage. HeapUsed is about 4M
> process.memoryUsage();
{ rss: 21106688.heapTotal: 7376896.heapUsed: 4153936.external: 9059 }

> let wm = new WeakMap(a);undefined

// Create a variable key that points to an array of 5*1024*1024
> let key = new Array(5 * 1024 * 1024);
undefined

// Set the key name of the WeakMap instance, which also points to the key array
// In this case, the key array is actually referenced twice,
// The variable key is referenced once, and the key name of WeakMap is referenced twice
// However, WeakMap is a weak reference. For the engine, the reference count is still 1
> wm.set(key, 1);
WeakMap {}

> global.gc();
undefined

// The heapUsed memory has been increased to 45MB
> process.memoryUsage();
{ rss: 67538944.heapTotal: 7376896.heapUsed: 45782816.external: 8945 }

// Clear the reference to array by variable key,
// But there is no manual clearing of the key name reference to the array of the WeakMap instance
> key = null;
null

// Perform the garbage collection again
> global.gc();
undefined

// The heapUsed memory is changed back to 4M.
// You can see that WeakMap's key name reference does not prevent GC from collecting memory
> process.memoryUsage();
{ rss: 20639744.heapTotal: 8425472.heapUsed: 3979792.external: 8956 }
Copy the code

In the above code, as long as the external reference disappears, the internal reference of WeakMap will be automatically cleared by garbage collection. Therefore, with the help of WeakMap, it is much easier to solve memory leaks.

The Dev Tools Memory panel of Chrome has a trash can button that forces garbage collect. This button can also be used to see if a reference disappears in a WeakMap.

4. The purpose of WeakMap

As mentioned earlier, the typical case of WeakMap application is DOM node as key name. Here’s an example.

let myWeakmap = new WeakMap(a); myWeakmap.set(document.getElementById('logo'),
  {timesClicked: 0});document.getElementById('logo').addEventListener('click'.function() {
  let logoData = myWeakmap.get(document.getElementById('logo'));
  logoData.timesClicked++;
}, false);
Copy the code

In the above code, document.getelementById (‘logo’) is a DOM node whose state is updated every time a click event occurs. We place this state in WeakMap as a key value, and the corresponding key name is the node object. Once the DOM node is removed, the state disappears automatically, with no risk of memory leaks.

Another use of WeakMap is to deploy private properties.

const _counter = new WeakMap(a);const _action = new WeakMap(a);class Countdown {
  constructor(counter, action) {
    _counter.set(this, counter);
    _action.set(this, action);
  }
  dec() {
    let counter = _counter.get(this);
    if (counter < 1) return;
    counter--;
    _counter.set(this, counter);
    if (counter === 0) {
      _action.get(this) (); }}}const c = new Countdown(2.() = > console.log('DONE'));

c.dec()
c.dec()
// DONE
Copy the code

In the code above, the two internal attributes of the Countdown class, _counter and _action, are weak references to the instance, so if the instance is deleted, they disappear without causing a memory leak.