If you’re following this series about ECMAScript 6, you’ve learned about some of the new methods available for the String
and Array
types. The new version of JavaScript introduces several new data types too. In this article we’ll discuss Map
and its weak counterpart WeakMap
.
Remember that if you want to polyfill what we’ll cover in this tutorial, you can employ es6-shim by Paul Miller.
Map
Maps are one of the most used data structures in programming. Maps are objects that associate a key to a value, regardless of the type of the value (number, string, object, and so on). For those of you who are not aware of maps, let’s discuss a brief example. In a typical structured database table you associate an ID with each entry (a row of the table). So, you have something like:
ID 1 -> Aurelio De Rosa, Italy ID 2 -> Colin Ihrig, USA ID 3 -> John Doe, USA
In languages like Java and C# you have a class that allows you to instantiate maps. In other languages like PHP you can create a map using an associative array. Prior to ECMAScript 6, JavaScript was one of the languages to lack this data structure. Now, this data type exists and it’s called Map
.
JavaScript maps are really powerful because they allow the use of any value (both objects and primitive values) either as a key or a value. This is one of the most important differences compared to maps created using the Object
type. In fact, maps created using an object literal only allow strings as their keys. In addition, as we’ll see in a moment, the Map
type has a method to easily retrieve the number of elements contained within it, while with objects you have to loop over them manually, checking that the element belongs to the object itself and it isn’t inherited (using the good old hasOwnProperty()
).
Now that I’ve introduced you to this new data type, let’s discover what are the properties and the methods available.
Map.prototype.size
The size
property returns the number of elements in the Map
object. This is a nice addition, that I mentioned in the previous section, because thanks this it you don’t have to count the elements by yourself.
Map.prototype.constructor()
The Map
object’s constructor is used to instantiate new objects and accepts an optional argument called iterable
. The latter is an array or an iterable object whose elements are key/value pairs (two-element arrays). Each of these elements will be added to the new map. For example, you could write:
var array = [['key1', 'value1'], ['key2', 100]];
var map = new Map(array);
Map.prototype.set()
The set()
method is used to add a new element (key/value pair) to a map. If the key used already exists, the value associated is replaced by the new one. Its signature is the following:
Map.prototype.set(key, value)
where key
is the key you want to use and value
is the value to store. This method modifies the map it’s called upon but also returns the new map.
This method is currently implemented in Firefox, Internet Explorer 11, and Chrome and Opera behind a flag (“Enable Experimental JavaScript”).
Map.prototype.get()
The get()
method returns the value associated with the key provided. If the key isn’t found, the method returns undefined
. The signature of the method is shown below, where key
is the key you want to use.
Map.prototype.get(key)
This method is currently implemented in Firefox, Internet Explorer 11, and Chrome and Opera behind a flag (“Enable Experimental JavaScript”).
Map.prototype.delete()
The delete()
method removes the element associated with the provided key from the map. It returns true
if the element is successfully removed or false
otherwise. The signature of this method is shown below:
Map.prototype.delete(key)
key
represents the key of the element you want to delete.
This method is currently implemented in Firefox, Internet Explorer 11, and Chrome and Opera (you have to activate the usual flag).
Map.prototype.has()
has()
is a method to verify if an element with the given key exists or not. It returns true
if the key is found or false
otherwise. The signature of this method is shown below:
Map.prototype.has(key)
where key
is the key you want to search.
This method is currently implemented in Firefox, Internet Explorer 11, and Chrome and Opera behind a flag (“Enable Experimental JavaScript”).
Map.prototype.clear()
The clear()
method is a convenient way to remove all the elements from a Map
object. The method doesn’t have a return value (which means it returns undefined
). The signature of clear()
is shown below:
Map.prototype.clear()
clear()
is currently implemented in Firefox, Internet Explorer 11, and Chrome and Opera behind the usual flag.
Map.prototype.forEach()
Just as we can loop over arrays, executing a callback function using the forEach()
method, the same is possible with maps. The signature of forEach()
is shown below:
Map.prototype.forEach(callback[, thisArg])
callback
is the callback function to execute for each of the elements in the map, and thisArg
is used to set the context (this
) of the callback. The method doesn’t have a return value (which means it returns undefined
). callback
receives three parameters that are:
value
: the value of the element processedkey
: the key of the element processedmap
: theMap
object being processed
This method is supported by Firefox, Internet Explorer 11, and Chrome and Opera behind a flag.
Map.prototype.entries()
entries()
is a method of obtaining an Iterator
object to iterate though the elements of the map. I’ve already mentioned this type of object when talking about the new keys()
method of the Array
type. The signature of this method is:
Map.prototype.entries()
This method is currently supported by Firefox, and Chrome and Opera behind a flag.
Map.prototype.keys()
The keys()
method is very similar to entries()
but it returns only the keys of the elements. Its signature is the following:
Map.prototype.keys()
This method is currently supported by Firefox, and Chrome and Opera behind a flag.
Map.prototype.values()
Similar to keys()
we have values()
. It returns an Iterator
object containing the values of the elements of the map. Its signature is the following:
Map.prototype.values()
This method is currently supported by Firefox, and Chrome and Opera behind a flag.
WeakMap
WeakMap
is very similar to Map
but has few important differences. The first is that a WeakMap
only accepts objects as keys. This means that {}
, function(){}
(remember that functions inherit from Object
), and instances of your own classes are allowed, but 'key'
, 10
, and other primitive data types are not.
The other important difference is that WeakMap
objects don’t prevent garbage collection if there aren’t any other references to an object which is acting as a key (the reference is weak). Due to this difference, there is no method to retrieve keys (for example the Map.prototype.keys()
method for Map
) or more than one element at once (like Map.prototype.values()
and Map.prototype.entries()
). The reason is well explained by the Mozilla developer network (MDN):
WeakMap keys are not enumerable (i.e. there is no method giving you a list of the keys). If they were, the list would depend on the state of garbage collection, introducing non-determinism.
As a further consequence of the previous point, there is no size
property available.
It’s also worth noting that Chrome 37 and Opera 24 (the latest stables at the time of writing) support WeakMap
and its methods without a flag, while the same isn’t true for Map
.
Putting it all together
So far you’ve learned all about the Map
and the WeakMap
data type and their methods. In this section we’ll put them in action so that you can have a better understanding of their power. In addition to showing you some code, we’ll also provide you with demos so that you can play with them live.
In the first demo we’ll see a Map
object and its methods in action.
// Creates a new Map object
var mapObj = new Map();
// Defines an object that will be used a key in the map
var objKey = {third: 'c'};
// Adds a new element having a String as its key and a String as its value
mapObj.set('first', 'a');
// Adds a new element having a Number as its key and an Array as its value
mapObj.set(2, ['b']);
// Adds a new element having an Object as its key and a Number as its value
mapObj.set(objKey, 3);
// Adds a new element having an Array as its key and a String as its value
mapObj.set(['crazy', 'stuff'], 'd');
// Checks whether an element having a key of "2" exists in the map. Prints "true"
console.log(mapObj.has(2));
// Checks whether an element having a key of "test" exists in the map. Prints "false"
console.log(mapObj.has('test'));
// Retrieves the element having key of "first". Prints "a"
console.log(mapObj.get('first'));
// Retrieves the element having key of "['crazy', 'stuff']". Prints "undefined" because even if the value of this array are identical to the one used to set a value, they are not the same array
console.log(mapObj.get(['crazy', 'stuff']));
// Retrieves the element having as a key the value of objKey. Prints "3" because it's exactly the same object using to set the element
console.log(mapObj.get(objKey));
// Retrieves the element having key of "empty". Prints "undefined"
console.log(mapObj.get('empty'));
// Retrieves the map size. Prints "4"
console.log(mapObj.size);
// Deletes the element having key of "first". Prints "true"
console.log(mapObj.delete('first'));
// Retrieves the map size. Prints "3"
console.log(mapObj.size);
// Loops over each element of the map
mapObj.forEach(function(value, key, map) {
// Prints both the value and the key
console.log('Value ' + value + ' is associated to key ' + key);
});
var entries = mapObj.entries();
var entry = entries.next();
// Loops over each element of the map
while(!entry.done) {
// Prints both the value and the key
console.log('Value ' + entry.value[1] + ' is associated to key ' + entry.value[0]);
entry = entries.next();
}
var values = mapObj.values();
var value = values.next();
// Loops over each value of the map
while(!value.done) {
// Prints the value
console.log('Value: ' + value.value);
value = values.next();
}
var keys = mapObj.keys();
var key = keys.next();
// Loops over each key of the map
while(!key.done) {
// Prints the key
console.log('Key: ' + key.value);
key = keys.next();
}
// Deletes all the elements of the map
mapObj.clear();
// Retrieves the map size. Prints "0"
console.log(mapObj.size);
A live demo of the previous code is shown below and also available as a JSFiddle.
In this second demo we’ll see how we can work with a WeakMap
object.
// Creates a new WeakMap object
var weakMapObj = new WeakMap();
// Defines an object that will be used a key in the map
var objKey1 = {a: 1};
// Defines another object that will be used a key in the map
var objKey2 = {b: 2};
// Adds a new element having an Object as its key and a String as its value
weakMapObj.set(objKey1, 'first');
// Adds a new element having an Object as its key and a String as its value
weakMapObj.set(objKey2, 'second');
// Adds a new element having a Function as its key and a Number as its value
weakMapObj.set(function(){}, 3);
// Checks whether an element having as its key the value of objKey1 exists in the weak map. Prints "true"
console.log(weakMapObj.has(objKey1));
// Retrieve the value of element associated with the key having the value of objKey1. Prints "first"
console.log(weakMapObj.get(objKey1));
// Deletes the element having key of objKey1. Prints "true"
console.log(weakMapObj.delete(objKey1));
// Deletes all the elements of the weak map
weakMapObj.clear();
A live demo of the previous code is shown below and also available as a JSFiddle.
Conclusion
In this tutorial I covered the new Map
and WeakMap
data types. The former is a nice addition to the language because most developers have simulated maps for a long time. Its weak counterpart isn’t really something you’ll use a lot in your day job, but there are surely situations where it might be a good fit. To reinforce the concepts discussed, I strongly encourage you to play with the demos provided. Have fun!
I'm a (full-stack) web and app developer with more than 5 years' experience programming for the web using HTML, CSS, Sass, JavaScript, and PHP. I'm an expert of JavaScript and HTML5 APIs but my interests include web security, accessibility, performance, and SEO. I'm also a regular writer for several networks, speaker, and author of the books jQuery in Action, third edition and Instant jQuery Selectors.