JavaScript
Article

Preparing for ECMAScript 6: Map and WeakMap

By Aurelio De Rosa

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 processed
  • key: the key of the element processed
  • map: the Map 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!

  • Christian Snodgrass

    Thanks for the article. I do think there is one small correction though. You mentioned that you can only use strings as keys for an array in JavaScript, hence you can’t use it as a true map. However, that isn’t quite true. I don’t know about the what the standard itself says, but in Chrome at least, it was perfectly happy to let me use any data type as a key, as long as I used the arr[] syntax (you can’t do it in one declaration). Here is a quick JSFiddle demo: http://jsfiddle.net/1gacx8ze/1/

    • jokeyrhyme

      It is possible that it’s coercing the value to a string before using it as a property name?

  • Guest

    Hi Christian.

    Thank you for your comment. I don’t where you read about this assertion because in the article I didn’t write it. I wrote that in Objects only strings can act as a key and this is true. For example if you try to write:

    {33: 11}

    You obtain an error.

  • Aurelio De Rosa

    Sorry, don’t know why my previous comment went as Guest. However, having seeing at your demo I understand what you meant but you’ve been fooled a bit here. It’s true that you can use Objects or other types but they will be converted into strings. So, if you write

    var map = {};
    var key1 = {};
    var key2 = { prop: 100 };
    map[key1] = ‘foo’;
    map[key2] = ‘bar’;
    console.log(map[key1]); // prints “bar”
    console.log(map[key2]); // prints “bar”

    The reason is that either key1 and key2 will be converted into their string equivalent that is “[object Object]”, so they are the same key.

    I hope this helps.

  • http://mlarocca.github.io Marcello La Rocca

    Besides what Aurelio pointed out in his answer, also be careful when you use numbers with plain objects: they will also be converted to strings, so

    var map = {};
    map[1] = 100;
    map[‘1’] = ‘fooling you’;
    console.log(map[1]);

    will not print the value you probably expected.
    Type coercion is probably the most error prone part of JS, and – if you are not careful – the trickiest one.

  • Mihai Manole

    There is no WeakMap.prototype.clear method in ES6 and I see no need for it. It’s safely to get rid of the old instance and take a new one.

  • Dmitry

    Hello man. can you write an article about this pattern http://fitzgeraldnick.com/weblog/53/.
    I didn’t understand it fully

  • Pooja Reddy

    Hi,

    I’m using Map in NodeJS 0.10.36 by enabling harmony flag. I’m able to create a map, set and get data, but other methods like size, keys(), entries(), forEach yield undefined results.

    var k = new Map();
    k.set(‘a’, 1);
    k.set(‘b’, 2);

    console.log(‘Printing out b’, k.get(‘b’)); //prints 2

    var leng = k.size;
    console.log(‘size on my map’, leng); // prints undefined

    for(var key in k.keys()) {
    console.log(‘keys value’,key);
    } // undefined

    Is there anything I might need to add to make these functionality work?

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in JavaScript, once a week, for free.