12/08/2018, 15:17

JavaScript (ES-2015) Set, Map, WeakSet and WeakMap

In ES-2015, new types of collections have appeared in JavaScript: Set, Map, WeakSet and WeakMap. Map Map is a collection for storing records like key: value. Unlike objects in which keys can only be strings, the Map key can have an arbitrary value, for example: 'use strict'; let map = new ...

In ES-2015, new types of collections have appeared in JavaScript: Set, Map, WeakSet and WeakMap.

Map

Map is a collection for storing records like key: value.

Unlike objects in which keys can only be strings, the Map key can have an arbitrary value, for example:

'use strict';

let map = new Map();

map.set('1', 'str1');   // key-string
map.set(1, 'num1');     // number
map.set(true, 'bool1'); // boolean value

// In an ordinary object this would be the same thing,
// map saved type of key
alert( map.get(1)   ); // 'num1'
alert( map.get('1') ); // 'str1'

alert( map.size ); // 3

As you can see from the example above, get and set methods are used to store and read values. Both keys and values are stored "as is", without type conversions.

The map.size property stores the total number of records in map.

The set method can be:

map
  .set('1', 'str1')
  .set(1, 'num1')
  .set(true, 'bool1');

When you create a Map, you can immediately initialize it with a list of values.

A map object with three keys, as in the example above:

let map = new Map([
  ['1',  'str1'],
  [1,    'num1'],
  [true, 'bool1']
]);

The argument to new Map must be an iterated object (not necessarily an array). Everywhere duck typing, maximum flexibility.

You can also use objects as map keys:

'use strict';

let user = { name: "Jamie" };

// For each user we will store the number of visits
let visitsCountMap = new Map();

// Object user is the key in в visitsCountMap
visitsCountMap.set(user, 123);

alert( visitsCountMap.get(user) ); // 123

Using objects as keys is just the case when Map is difficult to replace with ordinary Object objects. After all, for ordinary objects, the key can only be a string.

How map compares keys?

To check the values for equivalence, use the SameValueZero algorithm. It is similar to strict equality ===, the difference is that NaN is considered equal to NaN. Therefore, the NaN value can also be used as a key.

This algorithm can not be changed or set its own comparison function.

Methods for deleting entries:

  • Map.delete (key) deletes the entry with the key key, returns true if such an entry was, otherwise false.
  • Map.clear () - deletes all records, clears map.

To verify the existence of the key:

  • map.has(key) – returns true, if key, else false.

Iteration

To iterate through the map, one of three methods is used:

  • Map.keys () - returns the iterated object for the keys,
  • Map.values () - returns the iterated object for values,
  • Map.entries () - returns the iterated object for records [key, value], it is used by default in for..of.

For example:

'use strict';

let recipeMap = new Map([
  ['Cucumber',   '500 gr'],
  ['Tomatoes', '350 gr'],
  ['Sour cream',   '50 gr']
]);

// loop by keys
for(let fruit of recipeMap.keys()) {
  alert(fruit); // Cucumber, Tomatoes, Sour cream
}

// loop by values [key, value]
for(let amount of recipeMap.values()) {
  alert(amount); // 500 gr, 350 gr, 50 gr
}

// loop by recoeds
for(let entry of recipeMap) { // same like recipeMap.entries()
  alert(entry); // Cucumber, 500 gr , etc., arrays by 2 value
}

The search goes in the same order as the insertion

The search is performed in the order of insertion. Map objects guarantee this, unlike the usual Object objects.

In addition, Map has a standard forEach method, similar to an array:

'use strict';

let recipeMap = new Map([
  ['Cucumber',   '500 gr'],
  ['Tomatoes', '350 gr'],
  ['Sour cream',   '50 gr']
]);

recipeMap.forEach( (value, key, map) => {
  alert(`${key}: ${value}`); // Cucumber: 500 gr, etc.
});

Set

Set is a collection for storing a set of values, and each value can occur only once.

For example, visitors come to us, and we would like to save everyone who came. In this case, repeated visits should not lead to duplicates, that is, each visitor needs to "count" exactly once.

Set for this perfectly suits:

'use strict';

let set = new Set();

let jamie = {name: "Jamie"};
let josh = {name: "Josh"};
let julie = {name: "Julie"};

// Visits, some users go many times
set.add(jamie);
set.add(josh);
set.add(julie);
set.add(jamie);
set.add(josh);

// set stores only unique values
alert( set.size ); // 3

set.forEach( user => alert(user.name ) ); // Jamie, Josh, Julie

In the example above, multiple additions of the same object to set do not create unnecessary copies.

Alternative Set are arrays with a duplicate search for each addition, but they are much worse in performance. Or you can use ordinary objects, where the key is some unique identifier of the visitor. But this is less convenient than a simple and intuitive Set.

Basic methods:

  • Set.add (item) - adds an item to the collection, returns set.
  • Set.delete (item) - removes item from the collection, returns true if it was there, otherwise false.
  • Set.has (item) - returns true if item is in the collection, otherwise false.
  • Set.clear () - clears the set.

The Set is enumerated through forEach or for..of similarly to Map:

'use strict';

let set = new Set(["orange", "apple", "banana"]);

// same: for(let value of set)
set.forEach((value, valueAgain, set) => {
  alert(value); // orange, then apple, then banana
});

Note that in Set, the function in .forEach has three arguments: value, once again the value, and then the set itself to be sorted. The value is repeated twice in the arguments.

This is done for compatibility with Map, where the .forEach function also has three arguments. But in Set the first two always match and contain the next value of the set.

WeakMap and WeakSet

WeakSet is a special kind of Set that does not prevent the garbage collector from deleting its elements. The same is WeakMap for Map.

That is, if an object is present only in the WeakSet / WeakMap - it is deleted from memory.

This is necessary for those situations where the main place for storing and using objects is somewhere else in the code place, and here we want to store "auxiliary" data for them, existing only while the object is alive.

For example, we have elements on a page or, for example, users, and we want to store auxiliary information for them, for example, event handlers or just data, but only valid as long as the object to which they relate exists.

If we place such data in the WeakMap, and the object is made a key, then they will be automatically deleted from memory when the element is deleted.

For example:

// Current active users
let activeUsers = [
  {name: "Jamie"},
  {name: "Josh"},
  {name: "Jolie"}
];

// Auxiliary information about them,
// Which is not directly included in the user object,
// And therefore stored separately
let weakMap = new WeakMap();

weakMap[activeUsers[0]] = 1;
weakMap[activeUsers[1]] = 2;
weakMap[activeUsers[2]] = 3;

alert( weakMap[activeUsers[0]] ); // 1

activeUsers.splice(0, 1); // Jamie is no longer active user

// weakMap Now contains only 2 items

activeUsers.splice(0, 1); // Josh is no longer active user

// weakMap Now contains only 1 item

Thus, WeakMap eliminates the need to manually delete auxiliary data when the main object is deleted.

WeakMap has a number of limitations:

  • There is no size property.
  • You can not enumerate elements with an iterator or forEach.
  • There is no clear () method.

In other words, WeakMap only works on writing (set, delete) and reading (get, has) elements for a particular key, and not as a full collection. You can not display all the contents of the WeakMap, there are no corresponding methods.

This is due to the fact that the contents of the WeakMap can be modified by the garbage collector at any time, regardless of the programmer. The garbage collector works by itself. It does not guarantee that it will clean the object immediately when it becomes possible. Equally, it does not guarantee the opposite. There is no specific moment when such clearing will occur exactly - this is determined by the internal algorithms of the collector and his information about the system.

Therefore, the content of WeakMap at an arbitrary moment, strictly speaking, is not defined. Maybe the garbage collector has already deleted some records, or maybe not. With this, as well as with the requirements for effective implementation of WeakMap, there is a lack of methods that access all records.

The same applies to WeakSet: you can add items, check their availability, but you can not get their list and even find out the quantity.

These restrictions may seem inconvenient, but in fact they do not prevent WeakMap / WeakSet from fulfilling its main task - to be a "secondary" data repository for objects whose current list (and themselves) is stored elsewhere.

Total

  • Map - a collection of records like key: value, better Object in that it always sorts in the insert order and allows any keys.
  • Set - a collection of unique elements, also allows for any keys.

The main application of Map is the situation when there are not enough string keys (you need to store the correspondences for object keys), or when the string key can be completely arbitrary.

For example, in an ordinary Object, you can not use "completely any" keys. There are built-in methods, and certainly there is a property named __proto__, which is reserved by the system. If the key name is given by the site visitor, then it can try to use this property, replace the prototype, and this, when running JavaScript on the server, can already lead to serious errors.

  • WeakMap and WeakSet are the "cut-down" options for Map / Set, which allow only "point-by-point" access to elements (for a particular key or value). They do not prevent garbage collection, that is, if the link to the object remains only in WeakSet / WeakMap - it will be deleted.
0