ES6 Set and Map data structure


May 08, 2021 13:00 ES6


Table of contents


1. Set

Basic usage

ES6 a new data structure Set It is 数组 array, but the values of members are unique and have no duplicate values.

Set itself is a 构造函数 generates the Set data structure.

  1. const s = new Set();
  2. [2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
  3. for (let i of s) {
  4. console.log(i);
  5. }
  6. // 2 3 5 4

The above code adds members to the Set structure through the add() method, and the results show that the Set structure does not add duplicate values.

The Set function can accept an array (or other data structure with an iterable interface) as an argument for initialization.

  1. // 例一
  2. const set = new Set([1, 2, 3, 4, 4]);
  3. [...set]
  4. // [1, 2, 3, 4]
  5. // 例二
  6. const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
  7. items.size // 5
  8. // 例三
  9. const set = new Set(document.querySelectorAll('div'));
  10. set.size // 56
  11. // 类似于
  12. const set = new Set();
  13. document
  14. .querySelectorAll('div')
  15. .forEach(div => set.add(div));
  16. set.size // 56

In the above code, examples one and two are set functions that accept arrays as arguments, and example three accepts objects like arrays as arguments.

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

  1. // 去除数组的重复成员
  2. [...new Set(array)]

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

  1. [...new Set('ababbc')].join('')
  2. // "abc"

Type conversion does not occur when you add values to Set so 5 and "5" are two different values. Set internally determines whether the two values are different, using 精确相等运算符 an algorithm called “Same-value-zero equality” which is similar to the Precision Equality operator, the main difference being that when you add a value to Set, you think that NaN is equal to yourself, and the Precision Equality operator thinks that NaN is not equal to yourself.

  1. let set = new Set();
  2. let a = NaN;
  3. let b = NaN;
  4. set.add(a);
  5. set.add(b);
  6. set // Set {NaN}

The above code adds NaN to the Set instance twice, but only one. This indicates that within Set, the two NaNs are equal.

In addition, the two objects are always not equal.

  1. let set = new Set();
  2. set.add({});
  3. set.size // 1
  4. set.add({});
  5. set.size // 2

The code above indicates that two empty objects are considered two values because they are not equal.

The properties and methods of the Set instance

Instances of the Set structure have the following properties.

  • Set.prototype.constructor: Constructor, the default is the Set function.
  • Set.prototype.size: Returns the total number of members of the Set instance.

The methods for Set instances fall into two broad categories: 操作方法 (for operating data) and the 遍历方法 (for traversing members). Here are four ways to do this.

  • Set.prototype.add (value): Add a value that returns the Set structure itself.
  • Set.prototype.delete (value): Delete a value and return a Boolean value to indicate whether the deletion was successful.
  • Set.prototype.has (value): Returns a Boolean value that indicates whether the value is a member of Set.
  • Set.prototype.clear(): Clear all members with no return value.

Examples of these properties and methods are as follows.

  1. s.add(1).add(2).add(2);
  2. // 注意2被加入了两次
  3. s.size // 2
  4. s.has(1) // true
  5. s.has(2) // true
  6. s.has(3) // false
  7. s.delete(2);
  8. s.has(2) // false

Here's a comparison to see if the Object structure and set structure are written differently in determining whether to include a key.

  1. // 对象的写法
  2. const properties = {
  3. 'width': 1,
  4. 'height': 1
  5. };
  6. if (properties[someName]) {
  7. // do something
  8. }
  9. // Set的写法
  10. const properties = new Set();
  11. properties.add('width');
  12. properties.add('height');
  13. if (properties.has(someName)) {
  14. // do something
  15. }

The Array.from method converts the Set structure into an array.

  1. const items = new Set([1, 2, 3, 4, 5]);
  2. const array = Array.from(items);

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

  1. function dedupe(array) {
  2. return Array.from(new Set(array));
  3. }
  4. dedupe([1, 1, 2, 3]) // [1, 2, 3]

Traverse the operation

Instances of set structures have four traversal methods that can be used to traverse members.

  • Set.prototype.keys(): The traverser that returns the key name
  • Set.prototype.values(): The traverser that returns the key value
  • Set.prototype.entries(): The traverser that returns the key value pair
  • Set.prototype.forEach(): Use the callback function to traverse each member

In particular, Set's Set order is 插入 Order. This feature is sometimes useful, such as using Set to save a list of callback functions, which guarantees that the call will be made in the order in which it was added.

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

keys values method, entries method all return 遍历器对象 objects (see chapter Iterator Objects for details). Because the Set structure does not have a key name, only the key value (or the key name and key value are the same keys values exactly the same.

  1. let set = new Set(['red', 'green', 'blue']);
  2. for (let item of set.keys()) {
  3. console.log(item);
  4. }
  5. // red
  6. // green
  7. // blue
  8. for (let item of set.values()) {
  9. console.log(item);
  10. }
  11. // red
  12. // green
  13. // blue
  14. for (let item of set.entries()) {
  15. console.log(item);
  16. }
  17. // ["red", "red"]
  18. // ["green", "green"]
  19. // ["blue", "blue"]

In the above code, entries method 键名 returns 键值 a traverser that includes both the key name and the key value, so that each time an array is output, its two members are exactly equal.

Set of the Set structure is 可遍历 by default, and its default traverser generator is its value method.

  1. Set.prototype[Symbol.iterator] === Set.prototype.values
  2. // true

This means that the values method values and used for...of through set.

  1. let set = new Set(['red', 'green', 'blue']);
  2. for (let x of set) {
  3. console.log(x);
  4. }
  5. // red
  6. // green
  7. // blue

(2) forEach()

Instances of the Set structure, like arrays, also forEach method that performs an operation on each member without returning a value.

  1. let set = new Set([1, 4, 9]);
  2. set.forEach((value, key) => console.log(key + ' : ' + value))
  3. // 1 : 1
  4. // 4 : 4
  5. // 9 : 9

As the code above explains, the parameters of the forEach method are a handler. T he parameters of the function are consistent with the forEach of the array, followed by the key value, key name, and the collection itself (the parameter was omitted in the example above). It is important to note here that the key name of the Set structure is the key value (both are the same value), so the first argument is always the same as the value of the second argument.

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

(3) Traversing the app

扩展运算符 ( ... I nternal use for...of so it can also be used Set structures.

  1. let set = new Set(['red', 'green', 'blue']);
  2. let arr = [...set];
  3. // ['red', 'green', 'blue']

扩展运算符 operator with the Set structure, you can remove duplicate members of the array.

  1. let arr = [3, 5, 2, 2, 5, 5];
  2. let unique = [...new Set(arr)];
  3. // [3, 5, 2]

Also, the map and filter filter can also be used indirectly for Set.

  1. let set = new Set([1, 2, 3]);
  2. set = new Set([...set].map(x => x * 2));
  3. // 返回Set结构:{2, 4, 6}
  4. let set = new Set([1, 2, 3, 4, 5]);
  5. set = new Set([...set].filter(x => (x % 2) == 0));
  6. // 返回Set结构:{2, 4}

So you can easily implement Union, Intersect, and Difference with Set.

  1. let a = new Set([1, 2, 3]);
  2. let b = new Set([4, 3, 2]);
  3. // 并集
  4. let union = new Set([...a, ...b]);
  5. // Set {1, 2, 3, 4}
  6. // 交集
  7. let intersect = new Set([...a].filter(x => b.has(x)));
  8. // set {2, 3}
  9. // (a 相对于 b 的)差集
  10. let difference = new Set([...a].filter(x => !b.has(x)));
  11. // Set {1}

If you want to change the original Set structure synchronously during the traversal operation, there is currently no direct method, but there are two workables. One uses the original Set structure to map out a new structure and then assigns it to the original Set structure;

  1. // 方法一
  2. let set = new Set([1, 2, 3]);
  3. set = new Set([...set].map(val => val * 2));
  4. // set的值是2, 4, 6
  5. // 方法二
  6. let set = new Set([1, 2, 3]);
  7. set = new Set(Array.from(set, val => val * 2));
  8. // set的值是2, 4, 6

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

2. WeakSet

Meaning

WeakSet structure is Set and is a collection of non-repeating values. However, it differs from Set in two.

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

  1. const ws = new WeakSet();
  2. ws.add(1)
  3. // TypeError: Invalid value used in weak set
  4. ws.add(Symbol())
  5. // TypeError: invalid value used in weak set

The above code attempted to add a value and Symbol value to WeakSet, which resulted in an error because WeakSet could only place objects.

Second, the objects in WeakSet are weak references, meaning that the garbage collection mechanism does not take into account weakSet's reference to the object, that is, if no other objects reference the object, the garbage collection mechanism automatically reclaims the memory occupied by the object, not considering that the object still exists in WeakSet.

This is because the garbage collection mechanism relies on a reference count, and if the number of references to a value is not 0, the garbage collection mechanism does not free up this memory. A t the end of the use of this value, you sometimes forget to undring, causing memory to not be freed, which can lead to memory leaks. R eferences in WeakSet do not count in the garbage collection mechanism, so there is no such problem. T herefore, WeakSet is suitable for temporarily storing a set of objects, as well as information that is bound to the object. As long as these objects disappear externally, their references in WeakSet disappear automatically.

Because of this feature above, the members of WeakSet are not suitable for reference because it disappears at any time. In addition, because of the number of members inside WeakSet, depending on whether the garbage collection mechanism is running, it is likely that the number of members before and after running is different, and when the garbage collection mechanism runs is unpredictable, ES6 states that WeakSet cannot be traversed.

The same features apply to the WeakMap structure to be described later in this chapter.

Grammar

WeakSet is a constructor 构造函数 use new command to create a WeakSet data structure.

  1. const ws = new WeakSet();

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

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

In the code above, a is an array with two members, both of which are arrays. Using a as an argument to the WeakSet constructor, the member of a automatically becomes a member of the WeakSet.

Note that the members of the a array become members of WeakSet, not the a array itself. This means that the members of the array can only be objects.

  1. const b = [3, 4];
  2. const ws = new WeakSet(b);
  3. // Uncaught TypeError: Invalid value used in weak set(…)

In the code above, if the member of array b is not an object, joining WeakSet will report an error.

The WeakSet structure has three methods.

  • WeakSet.prototype.add(value): Add a new member to the WeakSet instance.
  • WeakSet.prototype.delete(value): Clears the specified member of the WeakSet instance.
  • WeakSet.prototype.has (value): Returns a Boolean value that indicates whether a value is in the WeakSet instance.

Here's an example.

  1. const ws = new WeakSet();
  2. const obj = {};
  3. const foo = {};
  4. ws.add(window);
  5. ws.add(obj);
  6. ws.has(window); // true
  7. ws.has(foo); // false
  8. ws.delete(window);
  9. ws.has(window); // false

WeakSet does not have a size property and there is no way to traverse its members.

  1. ws.size // undefined
  2. ws.forEach // undefined
  3. ws.forEach(function(item){ console.log('WeakSet has ' + item)})
  4. // TypeError: undefined is not a function

The above code attempted to get the size and forEach properties, and the results were not successful.

WeakSet cannot be traversed because members are weak references that can disappear at any time, and the traversal mechanism does not guarantee the existence of members, and it is likely that members will not be available as soon as the traversal is over. One use of WeakSet is to store DOM nodes without worrying about memory leaks when they are removed from the document.

Here's another example of WeakSet.

  1. const foos = new WeakSet()
  2. class Foo {
  3. constructor() {
  4. foos.add(this)
  5. }
  6. method () {
  7. if (!foos.has(this)) {
  8. throw new TypeError('Foo.prototype.method 只能在Foo的实例上调用!');
  9. }
  10. }
  11. }

The above code guarantees that Foo's instance method can only be called on Foo's instance. The benefit of using WeakSet here is that foos references to instances are not counted in the memory recycling mechanism, so when deleting instances, there is no need to consider foos and no memory leaks.

Map

Meaning and basic usage

JavaScript's 对象 (Object) is essentially 键值对 key-value pairs (Hash structure), but traditionally only strings are used 字符串 keys. This has put a lot of restrictions on its use.

  1. const data = {};
  2. const element = document.getElementById('myDiv');
  3. data[element] = 'metadata';
  4. data['[object HTMLDivElement]'] // "metadata"

The code above was intended to use a DOM node as the key to the object data, but because the object only accepts strings as key names, element is automatically converted to strings . . . object HTMLDivElement.

To solve this problem, ES6 provides a Map data structure. I t is similar to an object and is a collection of key value pairs, but the scope of a key is not limited to strings, and various types of values, including objects, can be used as keys. T hat is, the Object structure provides a "string-value" correspondence, and the Map structure provides a "value-value" correspondence, which is a better Hash structure implementation. Map is more appropriate than Object if you need a "key value pair" data structure.

  1. const m = new Map();
  2. const o = {p: 'Hello World'};
  3. m.set(o, 'content')
  4. m.get(o) // "content"
  5. m.has(o) // true
  6. m.delete(o) // true
  7. m.has(o) // false

The above code uses the set method of the Map structure, treats object o as a key to 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 map. A s a constructor, Map can also accept an array as an argument. The members of the array are arrays that represent key value pairs.

  1. const map = new Map([
  2. ['name', '张三'],
  3. ['title', 'Author']
  4. ]);
  5. map.size // 2
  6. map.has('name') // true
  7. map.get('name') // "张三"
  8. map.has('title') // true
  9. map.get('title') // "Author"

When the above code creates a new Map instance, it specifies two keys name and title.

The Map constructor accepts the array as an argument and actually executes the following algorithm.

  1. const items = [
  2. ['name', '张三'],
  3. ['title', 'Author']
  4. ];
  5. const map = new Map();
  6. items.forEach(
  7. ([key, value]) => map.set(key, value)
  8. );

In fact, it's not just arrays, any data structure of an array with an Iterator interface and each member is a two-element array (see Chapter Iterator) can be used as an argument to the Map constructor. This means that both Set and Map can be used to generate new Map.

  1. const set = new Set([
  2. ['foo', 1],
  3. ['bar', 2]
  4. ]);
  5. const m1 = new Map(set);
  6. m1.get('foo') // 1
  7. const m2 = new Map([['baz', 3]]);
  8. const m3 = new Map(m2);
  9. m3.get('baz') // 3

In the code above, we use Set Map respectively, as arguments to the Map constructor, and the result is that the new Map object is generated.

If you assign more than one value to the same key, the subsequent value overrides the previous value.

  1. const map = new Map();
  2. map
  3. .set(1, 'aaa')
  4. .set(1, 'bbb');
  5. map.get(1) // "bbb"

The above code assigns key 1 twice in a row, and the 1st value overrides the previous value.

If an unknown key is read, undefined is returned.

  1. new Map().get('asfddfsasadf')
  2. // undefined

Note that the Map structure treats the same object as the same key only if it is referenced. Be very careful about that.

  1. const map = new Map();
  2. map.set(['a'], 555);
  3. map.get(['a']) // undefined

The set and get methods in the code above are surfaced for the same key, but in fact these are two different array instances with different memory addresses, so the get method cannot read the key and returns undefined.

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

  1. const map = new Map();
  2. const k1 = ['a'];
  3. const k2 = ['a'];
  4. map
  5. .set(k1, 111)
  6. .set(k2, 222);
  7. map.get(k1) // 111
  8. map.get(k2) // 222

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

As you know, Map's keys are actually bound to memory addresses, which are considered two keys as long as the memory addresses are different. This solves the problem of collision of properties of the same name, and when we extend someone else's library, if we use the object as the key name, we don't have to worry about our own property having the same name as the original author's property.

If map's key is a simple type of value (number, string, Boolean value), Map treats it as a key, such as 0 and -0, as long as the two values are strictly equal, and Boolean true and string true are two different keys. I n addition, undefined and null are two different keys. Although NaN is not strictly equivalent to itself, Map treats it as the same key.

  1. let map = new Map();
  2. map.set(-0, 123);
  3. map.get(+0) // 123
  4. map.set(true, 1);
  5. map.set('true', 2);
  6. map.get(true) // 1
  7. map.set(undefined, 3);
  8. map.set(null, 4);
  9. map.get(undefined) // 3
  10. map.set(NaN, 123);
  11. map.get(NaN) // 123

The properties and operation methods of the instance

Examples of map structures have the following properties and methods of operation.

(1) size property

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

  1. const map = new Map();
  2. map.set('foo', true);
  3. map.set('bar', false);
  4. map.size // 2

(2)Map.prototype.set(key, value)

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

  1. const m = new Map();
  2. m.set('edition', 6) // 键是字符串
  3. m.set(262, 'standard') // 键是数值
  4. m.set(undefined, 'nah') // 键是 undefined

The set method returns the current Map object, so you can write in chains.

  1. let map = new Map()
  2. .set(1, 'a')
  3. .set(2, 'b')
  4. .set(3, 'c');

(3)Map.prototype.get(key)

The get method reads the key value corresponding to the key and returns the undefined if the key cannot be found.

  1. const m = new Map();
  2. const hello = function() {console.log('hello');};
  3. m.set(hello, 'Hello ES6!') // 键是函数
  4. m.get(hello) // Hello ES6!

(4)Map.prototype.has(key)

The has method returns a Boolean value that indicates whether a key is in the current Map object.

  1. const m = new Map();
  2. m.set('edition', 6);
  3. m.set(262, 'standard');
  4. m.set(undefined, 'nah');
  5. m.has('edition') // true
  6. m.has('years') // false
  7. m.has(262) // true
  8. m.has(undefined) // true

(5)Map.prototype.delete(key)

The delete method removes a key and returns true. If the deletion fails, false is returned.

  1. const m = new Map();
  2. m.set(undefined, 'nah');
  3. m.has(undefined) // true
  4. m.delete(undefined)
  5. m.has(undefined) // false

(6)Map.prototype.clear()

The clear method clears all members and does not return a value.

  1. let map = new Map();
  2. map.set('foo', true);
  3. map.set('bar', false);
  4. map.size // 2
  5. map.clear()
  6. map.size // 0

The traversal method

Map structure native provides three traverser 生成函数 traversal 遍历方法

  • Map.prototype.keys(): The traverser that returns the key name.
  • Map.prototype.values(): The traverser that returns the key value.
  • Map.prototype.entries(): Returns the traverser for all members.
  • Map.prototype.forEach(): Traverses all members of the Map.

It is important to note that the Map order of map is 插入顺序

  1. const map = new Map([
  2. ['F', 'no'],
  3. ['T', 'yes'],
  4. ]);
  5. for (let key of map.keys()) {
  6. console.log(key);
  7. }
  8. // "F"
  9. // "T"
  10. for (let value of map.values()) {
  11. console.log(value);
  12. }
  13. // "no"
  14. // "yes"
  15. for (let item of map.entries()) {
  16. console.log(item[0], item[1]);
  17. }
  18. // "F" "no"
  19. // "T" "yes"
  20. // 或者
  21. for (let [key, value] of map.entries()) {
  22. console.log(key, value);
  23. }
  24. // "F" "no"
  25. // "T" "yes"
  26. // 等同于使用map.entries()
  27. for (let [key, value] of map) {
  28. console.log(key, value);
  29. }
  30. // "F" "no"
  31. // "T" "yes"

The last example of the code above, which represents the default traverser interface (Symbol.iterator property) of the Map structure, is the entries method.

  1. map[Symbol.iterator] === map.entries
  2. // true

The Map structure is converted to an array structure, and the quicker way to do this is to use an extension operator (... )。

  1. const map = new Map([
  2. [1, 'one'],
  3. [2, 'two'],
  4. [3, 'three'],
  5. ]);
  6. [...map.keys()]
  7. // [1, 2, 3]
  8. [...map.values()]
  9. // ['one', 'two', 'three']
  10. [...map.entries()]
  11. // [[1,'one'], [2, 'two'], [3, 'three']]
  12. [...map]
  13. // [[1,'one'], [2, 'two'], [3, 'three']]

Combined with the array's map method, the filter method, you can implement map traversal and filtering (map itself does not have a map and filter method).

  1. const map0 = new Map()
  2. .set(1, 'a')
  3. .set(2, 'b')
  4. .set(3, 'c');
  5. const map1 = new Map(
  6. [...map0].filter(([k, v]) => k < 3)
  7. );
  8. // 产生 Map 结构 {1 => 'a', 2 => 'b'}
  9. const map2 = new Map(
  10. [...map0].map(([k, v]) => [k * 2, '_' + v])
  11. );
  12. // 产生 Map 结构 {2 => '_a', 4 => '_b', 6 => '_c'}

In addition, Map has a forEach method, similar to the array's forEach method, that can also be traversed.

  1. map.forEach(function(value, key, map) {
  2. console.log("Key: %s, Value: %s", key, value);
  3. });

The forEach method can also accept a second argument to bind this .

  1. const reporter = {
  2. report: function(key, value) {
  3. console.log("Key: %s, Value: %s", key, value);
  4. }
  5. };
  6. map.forEach(function(value, key, map) {
  7. this.report(key, value);
  8. }, reporter);

In the code above, this of the callback function for the Each method points to the reporter.

Transform with other data structures

(1) Map becomes an array

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

  1. const myMap = new Map()
  2. .set(true, 7)
  3. .set({foo: 3}, ['abc']);
  4. [...myMap]
  5. // [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]

(2) Array to Map

By passing the array into the Map constructor, you can convert it to Map.

  1. new Map([
  2. [true, 7],
  3. [{foo: 3}, ['abc']]
  4. ])
  5. // Map {
  6. // true => 7,
  7. // Object {foo: 3} => ['abc']
  8. // }

(3) Map becomes an object

If all of Map's keys are strings, it can be turned into objects without loss.

  1. function strMapToObj(strMap) {
  2. let obj = Object.create(null);
  3. for (let [k,v] of strMap) {
  4. obj[k] = v;
  5. }
  6. return obj;
  7. }
  8. const myMap = new Map()
  9. .set('yes', true)
  10. .set('no', false);
  11. strMapToObj(myMap)
  12. // { yes: true, no: false }

If there is a key name that is not a string, the key name is converted to a string and then used as the key name of the object.

(4) The object is converted to Map

The object can be converted to Map through Object.entries().

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

In addition, you can implement a conversion function yourself.

  1. function objToStrMap(obj) {
  2. let strMap = new Map();
  3. for (let k of Object.keys(obj)) {
  4. strMap.set(k, obj[k]);
  5. }
  6. return strMap;
  7. }
  8. objToStrMap({yes: true, no: false})
  9. // Map {"yes" => true, "no" => false}

(5) Map becomes JSON

Map to JSON distinguishes between two situations. In one case, map's key names are strings, at which point you can choose to convert to object JSON.

  1. function strMapToJson(strMap) {
  2. return JSON.stringify(strMapToObj(strMap));
  3. }
  4. let myMap = new Map().set('yes', true).set('no', false);
  5. strMapToJson(myMap)
  6. // '{"yes":true,"no":false}'

In another case, Map has a key name that is non-string, at which point you can choose to convert to array JSON.

  1. function mapToArrayJson(map) {
  2. return JSON.stringify([...map]);
  3. }
  4. let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
  5. mapToArrayJson(myMap)
  6. // '[[true,7],[{"foo":3},["abc"]]]'

(6) JSON turns to Map

JSON turns to Map, and normally all key names are strings.

  1. function jsonToStrMap(jsonStr) {
  2. return objToStrMap(JSON.parse(jsonStr));
  3. }
  4. jsonToStrMap('{"yes": true, "no": false}')
  5. // Map {'yes' => true, 'no' => false}

However, there is a special case where the entire JSON is an array, and each array member itself is an array with two members. A t this point, it can be converted to Map one by one. This is often an inverse operation of map-to-array JSON.

  1. function jsonToMap(jsonStr) {
  2. return new Map(JSON.parse(jsonStr));
  3. }
  4. jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
  5. // Map {true => 7, Object {foo: 3} => ['abc']}

4. WeakMap

Meaning

WeakMap structure is similar to the Map structure and is also used to 键值对 pairs.

  1. // WeakMap 可以使用 set 方法添加成员
  2. const wm1 = new WeakMap();
  3. const key = {foo: 1};
  4. wm1.set(key, 2);
  5. wm1.get(key) // 2
  6. // WeakMap 也可以接受一个数组,
  7. // 作为构造函数的参数
  8. const k1 = [1, 2, 3];
  9. const k2 = [4, 5, 6];
  10. const wm2 = new WeakMap([[k1, 'foo'], [k2, 'bar']]);
  11. wm2.get(k2) // "bar"

There are two differences between WeakMap and Map.

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

  1. const map = new WeakMap();
  2. map.set(1, 2)
  3. // TypeError: 1 is not an object!
  4. map.set(Symbol(), 2)
  5. // TypeError: Invalid value used as weak map key
  6. map.set(null, 2)
  7. // TypeError: Invalid value used as weak map key

In the code above, if you use the values 1 and Symbol as the key names for WeakMap, you will report an error.

Second, the object to which WeakMap's key name points does not count toward the garbage collection mechanism.

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

  1. const e1 = document.getElementById('foo');
  2. const e2 = document.getElementById('bar');
  3. const arr = [
  4. [e1, 'foo 元素'],
  5. [e2, 'bar 元素'],
  6. ];

In the code above, e1 and e2 are two objects, and we add some text descriptions to them through the arr array. This results in arr references 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 up the memory consumed by e1 and e2.

  1. // 不需要 e1 和 e2 的时候
  2. // 必须手动删除引用
  3. arr [0] = null;
  4. arr [1] = null;

It's obviously inconvenient to write like this. Once you forget to write, there is a memory leak.

WeakMap was born to solve this problem, and its key names refer to objects that are weak references, i.e. garbage collection mechanisms do not take that reference into account. T herefore, as long as the other references to the referenced object are cleared, the garbage collection mechanism frees up the memory occupied by the object. That is, once no longer needed, the key name objects and corresponding key value pairs in WeakMap automatically disappear without manually deleting the reference.

Basically, you can use WeakMap if you want to add data to an object without interfering with the garbage collection mechanism. A typical scenario is to add data to the DOM element of a Web page and you can use the WeakMap structure. When the DOM element is cleared, its corresponding WeakMap record is automatically removed.

  1. const wm = new WeakMap();
  2. const element = document.getElementById('example');
  3. wm.set(element, 'some information');
  4. wm.get(element) // "some information"

In the code above, create a new Weakmap instance first. A DOM node is then stored as a key name in the instance, and some additional information is stored as a key value in WeakMap. At this point, the reference to element in WeakMap is a weak reference and is not counted as a garbage collection mechanism.

That is, the reference count for the DOM node object above is 1, not 2. A t this point, once the reference to the node is eliminated, the memory it consumes is freed by the garbage collection mechanism. This key value pair saved by Weakmap also disappears automatically.

In short, WeakMap's special case is that the object to which its key corresponds may disappear in the future. The WeakMap structure helps prevent memory leaks.

Note that WeakMap weakly refers to only the key name, not the key value. The key value is still a normal reference.

  1. const wm = new WeakMap();
  2. let key = {};
  3. let obj = {foo: 1};
  4. wm.set(key, obj);
  5. obj = null;
  6. wm.get(key)
  7. // Object {foo: 1}

In the code above, the key value obj is a normal reference. Therefore, even if the reference to obj is eliminated outside of WeakMap, the reference inside WeakMap still exists.

The syntax of WeakMap

The main difference between WeakMap and 遍历操作 Map in the API is two, one is that there is no traversal operation (i.e., no keys(), values() and entries() methods, and size property. B ecause there is no way to list all key names, whether a key name exists is completely unpredictable and is related to whether the garbage collection mechanism is working. T his moment can get the key name, the next moment garbage collection mechanism suddenly run, this key name is gone, in order to prevent uncertainty, the unified provisions can not get the key name. T he second is that the clear method is not supported if it cannot be emptied. Therefore, WeakMap has only four methods available: get(), set(), has(), delete().

  1. const wm = new WeakMap();
  2. // size、forEach、clear 方法都不存在
  3. wm.size // undefined
  4. wm.forEach // undefined
  5. wm.clear // undefined

The purpose of WeakMap

As you said earlier, a typical scenario for a WeakMap application is DOM 节点 is the key 键名 Here's an example.

  1. let myWeakmap = new WeakMap();
  2. myWeakmap.set(
  3. document.getElementById('logo'),
  4. {timesClicked: 0})
  5. ;
  6. document.getElementById('logo').addEventListener('click', function() {
  7. let logoData = myWeakmap.get(document.getElementById('logo'));
  8. logoData.timesClicked++;
  9. }, false);

In the code above, document.getElementById ('logo') is a DOM node that updates its status whenever a click event occurs. W e put this state as a key value in WeakMap, and the corresponding key name is the node object. Once this DOM node is removed, the state automatically disappears and there is no risk of memory leakage.

Another use of WeakMap is to deploy private properties.

  1. const _counter = new WeakMap();
  2. const _action = new WeakMap();
  3. class Countdown {
  4. constructor(counter, action) {
  5. _counter.set(this, counter);
  6. _action.set(this, action);
  7. }
  8. dec() {
  9. let counter = _counter.get(this);
  10. if (counter < 1) return;
  11. counter--;
  12. _counter.set(this, counter);
  13. if (counter === 0) {
  14. _action.get(this)();
  15. }
  16. }
  17. }
  18. const c = new Countdown(2, () => console.log('DONE'));
  19. c.dec()
  20. c.dec()
  21. // DONE

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