Working with maps in JavaScript
Let's learn about the map data structure in JavaScript
By: Ajdin Imsirovic 02 December 2019
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.
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:
- value
- key
- 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.