In yesterday’s article, we looked at sets in JavaScript. In this article, we’ll look at maps, another newer data structure in JS, added to the language in the ES6 update.

Working with maps in JavaScript Image by CodingExercises

A map is like a “countable” object (i.e. a dictionary)

Maps seem almost the same as JavaScript objects. However, there are some things specific to maps:

  • In maps, any type of data can be used as a key (contrary to only strings as keys in JavaScript objects). For example, it’s completely normal to use numbers as keys in a JavaScript map.
  • Maps are built for setting and getting key-value pairs. No prototypes and prototypal inheritance in maps! That’s why maps are a great choice when you just need a simple data storage with easy look up.
  • In objects, there’s no straightforward approach to getting the number of an object’s key-value pairs. Maps come with the size property. Counting a map’s key-value pairs is trivial.
  • In objects, to access the properties we simply use the dot operator or the brackets notation; in maps, we can get back values using the get method

Using the map data structure in JS

We can build a new map using the Map() constructor.

let players = new Map();
players; // returns: Map(0) {}

Populating a map data structure in JS

To populate a map, we use the set method. We pass the key and its value to the set method like this:

players.set(77, "Luka Doncic");
players; // Map(1) {77 => "Luka Doncic"}

Retrieving all the key-value pairs from a map

To get back all the key-value pairs from the players map, we just type players, as seen above. The returned key-value pairs are separated with the => sign, also known as “fat arrow” (or “hash rocket” in Ruby). On the left of the fat arrow is the key, and on the right is the value.

Passing more than one key-value pairs to a new map

There is a way to pass multiple key-value pairs to a new map, using an array of arrays:

let players2 = new Map(
    [ 
        [77, "Luka Doncic"], 
        [23, "Michael Jordan"], 
        [34, "Shaquille O'Neal"], 
    ]
)
let colors = new Map(
    [ 
        ["tomato","#ff6347"],
        ["royalblue","#4169e1"],
        ["darkgreen","#006400"] 
    ]
);

What is the size of a specific map?

To find out the number of key-value pairs in a map, we use the size method, like this:

colors.size(); // returns: 3

Adding key-value pairs to existing maps

For each additional key-value pair, we can run another set method:

players.set(23, "Michael Jordan").set(34, "Shaquille O'Neal");
players; // returns: Map(3) {77 => "Luka Doncic", 23 => "Michael Jordan", 34 => "Shaquille O'Neal"}

Does a key exist in a map?

To check if a key exists in a map, we use the has method:

players.has(23); // returns: true
colors.has("tomato"); // returns: true

Returning a value from a map key in JS

The has method returns true if the key we pass it exists in our map. Otherwise, it returns false.

To look up a value in the map, we use the get method:

players.get(23); // returns: "Michael Jordan"
colors.get("tomato"); // returns: "#ff6347"

Deleting key-value pairs from maps in JS

To remove a key-value pair from a map, we use the delete method, passing it the key to delete:

colors.delete("tomato"); // returns: true

The delete method returns true if the key was found (and thus successfully deleted). Otherwise, it returns false:

colors.delete("orange"); // returns: false

Clearing a map from all key-value pairs

To remove all the key-value pairs at once, use the clear method:

colors.clear(); // returns: undefined
colors; // returns: Map(0) {}

Running the clear method on a map makes that map have zero key-value pairs.

Convert a map to an array

To convert a map to an array, we can:

  • use a spread operator
  • use the Array.from method

With the spread operator, it’s simple:

let arr = [...players]; // returns: undefined
arr; // returns an array of values from the original map in the form of an array

Here’s the full output of arr in the console:

(3) [Array(2), Array(2), Array(2)]
    0: (2) [77, "Luka Doncic"]
    1: (2) [23, "Michael Jordan"]
    2: (2) [34, "Shaquille O'Neal"]
    length: 3
    __proto__: Array(0)

It’s also easy to use the Array.from method:

let arr = Array.from(players);
arr; // returns an array of values from the original set in the form of an array

Convert a map to an object

There’s a lively discussion about converting a map to an object, available in a comments section of this gist.

From the various suggestions and solutions of converting a map to an object, one simple solution sort of stands out, in my opinion:

const obj = {};
map.forEach((value, key) => (obj[key] = value));

Here’s the above code put in practice with our players example map:

let players = new Map([ 
    [77, "Luka Doncic"],
    [23, "Michael Jordan"],
    [34, "Shaquille O'Neal"],
]);

const obj = {};

players.forEach( (value, key) => (obj[key] = value) );

obj;
/*
returns:
{ 23: "Michael Jordan", 34: "Shaquille O'Neal", 77: "Luka Doncic" }
*/

Comparing maps and objects

In JS, an object stores key-value pairs, where the object’s key has to be: string, number, or symbol. An object doesn’t remember the order in which its keys were inserted, and thus an object is considered a non-ordered data structure.

An ES6 map object also stores key-value pairs, but each key is unique. A map’s key can be any data type. A map is iterable. Using a loop, such as a for...of, we can iterate over map object’s elements.

An object is not ordered and not iterable. A map is ordered and iterable.

More info on when to use one or the other can be found on this Stackoverflow thread.

Weak maps in JS

Similar to weak sets, weak maps allow the garbage collector to remove any members of the map that had the original object deleted.

To make a weak map, you use this syntax:

let players3 = new WeakMap(); // returns: undefined
players3; // returns: WeakMap {}

All the mentioned methods that exist on a map in JS, also exist on a weak map object: has, get, set, and delete.

A weak map object cannot have a primitive as its key, contrary to a map object which doesn’t have this limitation.

Additionally, a weak map doesn’t have the size property, like a regular map object does.

Weak maps and weak sets help with memory use

One of the strengths of weak maps and weak sets is that due to the way that they’re set up, they help us avoid memory leaks.

Looping over maps

To loop over maps, use the for of or forEach.

Here’s an example of a map:

let numMap = new Map([
    [1,"one"],
    [2,"two"],
    [3,"three"]
]);

Maps have a keys method, which allows us to access the keys. For example:

console.log(numMap.keys());

Running the above returns:

MapIterator {1,2,3}

In Chrome, there’s a little triangle at the very beginning of the above line of code. Clicking it opens more details:

[[Entries]]
    0: 1
    1: 2
    2: 3
    __proto__: Map Iterator
    [[IteratorHasMore]]: true
    [[IteratorIndex]]: 0
    [[IteratorKind]]: "keys"

We can now iterate over the keys, with:

for ( k of numMap.keys() ) {
    console.log(k);
}

There’s also the values method, so we can iterate over values with:

for ( v of numMap.values() ) {
    console.log(v);
}

The above will return:

one
two
three

Looping over a map with a forEach

To loop over a map with a forEach, we need to understand what arguments can be passed to its callback function.

A map’s forEach method’s callback is run with three arguments:

  1. value
  2. key
  3. the map being iterated over

Here’s an example:

let numMap = new Map([
    ["breakfast", "eggs"],
    ["lunch", "fish"],
    ["dinner", "an apple"],
]);

numMap.forEach( (value, key, map) => {
    console.log( `
        For ${key} we had ${value}.
    `)
});

Running the above code will output:

For breakfast we had eggs.
For lunch we had fish.
For dinner we had an apple.

While maps are enumerable, weak maps are not. As it is right now, in the first half of 2020, there seems to be no easy way to loop over weak maps in JS.