Among others, the following four data structures are new in ECMAScript 6:
The keys of a Map can be arbitrary values:
You can use an Array (or any iterable) with [key, value] pairs to set up the initial data in the Map:
A Set is a collection of unique elements:
As you can see, you can initialize a Set with elements if you hand the constructor an iterable (
arr in the example) over those elements.
A WeakMap is a Map that doesn’t prevent its keys from being garbage-collected. That means that you can associate data with objects without having to worry about memory leaks. For example:
Map data structure in ECMAScript 6 lets you use arbitrary values as keys and is highly welcome.
Working with single entries:
Determining the size of a Map and clearing it:
You can set up a Map via an iterable over key-value “pairs” (Arrays with 2 elements). One possibility is to use an Array (which is iterable):
set() method is chainable:
Any value can be a key, even an object:
Most Map operations need to check whether a value is equal to one of the keys. They do so via the internal operation SameValueZero, which works like
===, but considers
NaN to be equal to itself.
Let’s first see how
Conversely, you can use
NaN as a key in Maps, just like any other value:
Different objects are always considered different. That is something that can’t be configured (yet), as explained later, in the FAQ.
Getting an unknown key produces
Let’s set up a Map to demonstrate how one can iterate over it.
Maps record the order in which elements are inserted and honor that order when iterating over keys, values or entries.
keys() returns an iterable over the keys in the Map:
values() returns an iterable over the values in the Map:
entries() returns the entries of the Map as an iterable over [key,value] pairs (Arrays).
Destructuring enables you to access the keys and values directly:
The default way of iterating over a Map is
Thus, you can make the previous code snippet even shorter:
The spread operator (
...) can turn an iterable into an Array. That lets us convert the result of
Map.prototype.keys() (an iterable) into an Array:
Maps are also iterable, which means that the spread operator can turn Maps into Arrays:
forEach has the following signature:
The signature of the first parameter mirrors the signature of the callback of
Array.prototype.forEach, which is why the value comes first.
filter() Arrays, but there are no such operations for Maps. The solution is:
I’ll use the following Map to demonstrate how that works.
Step 1 is performed by the spread operator (
...) which I have explained previously.
There are no methods for combining Maps, which is why the approach from the previous section must be used to do so.
Let’s combine the following two Maps:
map2, I turn them into Arrays via the spread operator (
...) and concatenate those Arrays. Afterwards, I convert the result back to a Map. All of that is done in the first line.
If a Map contains arbitrary (JSON-compatible) data, we can convert it to JSON by encoding it as an Array of key-value pairs (2-element Arrays). Let’s examine first how to achieve that encoding.
The spread operator lets you convert a Map to an Array of pairs:
Map constructor lets you convert an Array of pairs to a Map:
Let’s use this knowledge to convert any Map with JSON-compatible data to JSON and back:
The following interaction demonstrates how these functions are used:
Whenever a Map only has strings as keys, you can convert it to JSON by encoding it as an object. Let’s examine first how to achieve that encoding.
The following two function convert string Maps to and from objects:
Let’s use these two functions:
With these helper functions, the conversion to JSON works as follows:
This is an example of using these functions:
new Map(entries? : Iterable<[any,any]>)
iterablethen an empty Map is created. If you do provide an iterable over [key, value] pairs then those pairs are used to add entries to the Map. For example:
Handling single entries:
Map.prototype.get(key) : any
keyis mapped to in this Map. If there is no key
keyin this Map,
Map.prototype.set(key, value) : this
key, it is updated. Otherwise, a new entry is created. This method returns
this, which means that you can chain it.
Map.prototype.has(key) : boolean
Map.prototype.delete(key) : boolean
key, it is removed and
trueis returned. Otherwise, nothing happens and
Handling all entries:
get Map.prototype.size : number
Map.prototype.clear() : void
Iterating and looping: happens in the order in which entries were added to a Map.
Map.prototype.entries() : Iterable<[any,any]>
Map.prototype.forEach((value, key, collection) => void, thisArg?) : void
thisis set to it for each invocation. Otherwise,
thisis set to
Map.prototype.keys() : Iterable<any>
Map.prototype.values() : Iterable<any>
Map.prototype[Symbol.iterator]() : Iterable<[any,any]>
WeakMaps work mostly like Maps, with the following differences:
The following sections explain each of these differences.
If you add an entry to a WeakMap then the key must be an object:
The keys in a WeakMap are weakly held: Normally, an object that isn’t referred to by any storage location (variable, property, etc.) can be garbage-collected. WeakMap keys do not count as storage locations in that sense. In other words: an object being a key in a WeakMap does not prevent the object being garbage-collected.
Additionally, once a key is gone, its entry will also disappear (eventually, but there is no way to detect when, anyway).
It is impossible to inspect the innards of a WeakMap, to get an overview of them. That includes not being able to iterate over keys, values or entries. Put differently: to get content out of a WeakMap, you need a key. There is no way to clear a WeakMap, either (as a work-around, you can create a completely new instance).
These restrictions enable a security property. Quoting Mark Miller: “The mapping from weakmap/key pair value can only be observed or affected by someone who has both the weakmap and the key. With
clear(), someone with only the WeakMap would’ve been able to affect the WeakMap-and-key-to-value mapping.”
Additionally, iteration would be difficult to implement, because you’d have to guarantee that keys remain weakly held.
WeakMaps are useful for associating data with objects whose life cycle you can’t (or don’t want to) control. In this section, we look at two examples:
With WeakMaps, you can associate previously computed results with objects, without having to worry about memory management. The following function
countOwnKeys is an example: it caches previous results in the WeakMap
If we use this function with an object
obj, you can see that the result is only computed for the first invocation, while a cached value is used for the second invocation:
Let’s say we want to attach listeners to objects without changing the objects. You’d be able to add listeners to an object
And you’d be able to trigger the listeners:
The two functions
triggerListeners() can be implemented as follows.
The advantage of using a WeakMap here is that, once an object is garbage-collected, its listeners will be garbage-collected, too. In other words: there won’t be any memory leaks.
In the following code, the WeakMaps
_action are used to store the data of virtual properties of instances of
More information on this technique is given in the chapter on classes.
The constructor and the four methods of
WeakMap work the same as their
ECMAScript 5 doesn’t have a Set data structure, either. There are two possible work-arounds:
indexOf(), remove elements via
filter(), etc. This is not a very fast solution, but it’s easy to implement. One issue to be aware of is that
indexOf()can’t find the value
ECMAScript 6 has the data structure
Set which works for arbitrary values, is fast and handles
Managing single elements:
Determining the size of a Set and clearing it:
You can set up a Set via an iterable over the elements that make up the Set. For example, via an Array:
add method is chainable:
new Set()has at most one argument
Set constructor has zero or one arguments:
Further arguments are ignored, which may lead to unexpected results:
For the second Set, only
'foo' is used (which is iterable) to define the Set.
As with Maps, elements are compared similarly to
===, with the exception of
NaN being like any other value.
Adding an element a second time has no effect:
===, two different objects are never considered equal (which can’t currently be customized, as explained later, in the FAQ, later):
Sets are iterable and the
for-of loop works as you’d expect:
As you can see, Sets preserve iteration order. That is, elements are always iterated over in the order in which they were inserted.
The previously explained spread operator (
...) works with iterables and thus lets you convert a Set to an Array:
We now have a concise way to convert an Array to a Set and back, which has the effect of eliminating duplicates from the Array:
In contrast to Arrays, Sets don’t have the methods
filter(). A work-around is to convert them to Arrays and back.
ECMAScript 6 Sets have no methods for computing the union (e.g.
addAll), intersection (e.g.
retainAll) or difference (e.g.
removeAll). This section explains how to work around that limitation.
b): create a Set that contains the elements of both Set
a and Set
The pattern is always the same:
The spread operator (
...) inserts the elements of something iterable (such as a Set) into an Array. Therefore,
[...a, ...b] means that
b are converted to Arrays and concatenated. It is equivalent to
b): create a Set that contains those elements of Set
a that are also in Set
a to an Array, filter the elements, convert the result to a Set.
b): create a Set that contains those elements of Set
a that are not in Set
b. This operation is also sometimes called minus (
new Set(elements? : Iterable<any>)
iterablethen an empty Set is created. If you do then the iterated values are added as elements to the Set. For example:
Single Set elements:
Set.prototype.add(value) : this
valueto this Set. This method returns
this, which means that it can be chained.
Set.prototype.has(value) : boolean
valueis in this Set.
Set.prototype.delete(value) : boolean
valuefrom this Set.
All Set elements:
get Set.prototype.size : number
Set.prototype.clear() : void
Iterating and looping:
Set.prototype.values() : Iterable<any>
Set.prototype[Symbol.iterator]() : Iterable<any>
Set.prototype.forEach((value, key, collection) => void, thisArg?)
keyare both set to the element, so that this method works similarly to
thisis set to it for each call. Otherwise,
thisis set to
Map: The following two methods only exist so that the interface of Sets is similar to the interface of Maps. Each Set element is handled as if it were a Map entry whose key and value are the element.
Set.prototype.entries() : Iterable<[any,any]>
Set.prototype.keys() : Iterable<any>
entries() allows you to convert a Set to a Map:
WeakSet is a Set that doesn’t prevent its elements from being garbage-collected. Consult the section on
WeakMap for an explanation of why WeakSets don’t allow iteration, looping and clearing.
Given that you can’t iterate over their elements, there are not that many use cases for WeakSets. They do enable you to mark objects.
For example, if you have a factory function for proxies, you can use a WeakSet to record which objects were created by that factory:
The complete example is shown in the chapter on proxies.
_proxies must be a WeakSet, because a normal Set would prevent a proxy from being garbage-collected once it isn’t referred to, anymore.
Domenic Denicola shows how a class
Foo can ensure that its methods are only applied to instances that were created by it:
The constructor and the three methods of
WeakSet work the same as their
Arrays have the property
length to count the number of entries. Maps and Sets have a different property,
The reason for this difference is that
length is for sequences, data structures that are indexable – like Arrays.
size is for collections that are primarily unordered – like Maps and Sets.
It would be nice if there were a way to configure what Map keys and what Set elements are considered equal. But that feature has been postponed, as it is difficult to implement properly and efficiently.
If you use a key to get something out of a Map, you’d occasionally like to specify a default value that is returned if the key is not in the Map. ES6 Maps don’t let you do this directly. But you can use the
Or operator (
||), as demonstrated in the following code.
countChars returns a Map that maps characters to numbers of occurrences.
In line A, the default
0 is used if
ch is not in the
If you map anything other than strings to any kind of data, you have no choice: you must use a Map.
If, however, you are mapping strings to arbitrary data, you must decide whether or not to use an object. A rough general guideline is:
Map keys mainly make sense if they are compared by value (the same “content” means that two values are considered equal, not the same identity). That excludes objects. There is one use case – externally attaching data to objects, but that use case is better served by WeakMaps where an entry goes away when the key disappears.