Getting going with ES6 Maps
As we move into a world where modern browsers are supporting the nearly the entire ES6 specification, we have a variety of data structures to use for storing and accessing our data as native built-in primitives. In ES6 we specifically gain access to 'keyed collections'. Keyed collections encompasses four built-ins, these are namely Set
, Map
, WeakSet
and WeakMap
. Here we'll be talking about the Map
in particular. Let's get stuck in!
What is a Map? #
A Map
is a collection of key value pairs, where both key and values can be of any type. This differs from how one might have traditionally used a JavaScript Object as a map, as historically keys have been strings only (this has changed with the introduction of Symbols but that's for a different blog post to come!).
const obj = { 1: "one" };
typeof Object.keys(obj)[0]; // Evaluates to string
So with Maps, we have the additional power of using anything we like as our key, even a HTMLElement
, which is powerful because it makes it easy to associate data with a DOM node. To illustrate the use of none String
keys, we can see this example using booleans:
const map = new Map([
[true, "Things that are true"],
[false, "Things that are false"],
]);
Here true
and false
are the respective keys in our collection. So how does JavaScript know how to evaluate something as matching a Map
key? Well the approach is very similar to the ===
(identity) operator, with the minor caveat that NaN
(Not a Number) equates to NaN
even though NaN !== NaN
.
How do we use it? #
Maps provide a set of methods for interfacing with them such as get
, set
and delete
. These methods provide a clean inteface for interacting with our data. Let's see how that works in practice:
const one = "one";
const map = new Map([["one", 1]]);
map.get("one"); // Returns 1
map.get(one); // Returns 1
map.set("two", 2);
// Returns the Map with "two" set to 2
map.delete("one");
// Returns true if successful or false if unsuccessful
map.clear();
// Returns undefined
As you can see there's a little gotcha here, get, set and delete all have different return behaviours. Get will return the associated value, set will return the Map
itself, and delete will return a boolean, and clear returns undefined
. Something to keep in mind!
We also have some really nice convenience methods to help us interface. For example, has
which will tell us if a key exists in a Map
:
const map = new Map([
["one", 1],
["two", 2],
]);
map.has("two"); // true
This is very useful for quickly checking the existence of key in a Map
. What about if we want to iterate over a Maps contents? Firstly there are a few convenient methods in this regard: values
, entries
and forEach
. Let's look at values and keys. Both these methods return an iterable. Iterables are a little outside the scope of this post, but think of them as JavaScript variables that have defined behaviours for what happens when you iterate over them (for example using for...of):
// Entries
const map = new Map([
["one", 1],
["two", 2],
]);
for (const num of map.entries()) {
console.log(num);
// Logs ["one", 1]
// Then ["two", 2]
}
// Values
for (const key of map.values()) {
console.log(key);
// Logs 1
// Then 2
}
Alongside this we have the forEach
method that some of you may be familiar with from the Array
method of the same name. It behaves like so:
const map = new Map([
["one", 1],
["two", 2],
]);
map.forEach((key, value) => console.log(key, value));
// Logs ["one", 1]
// Then ["two", 2]
Lastly we can quickly and conveniently get the size of a map with the size
method. It is fairly straight forward and operates like so:
const pets = Map([
["dog", "woof!"],
["cat", "meow"],
["goldfish", "bop bop bop"],
]);
animals.size(); // Evaluates to 3
Which would have been less elegant using an Object
:
var pets = {"dog", "woof!", "cat": "meow", "goldfish": "bop bop bop"};
Object.keys(pets).length;
Why use a Map? #
Simply put, the Map
is more idiomatic and effective primitive than an Object
for use as a mutable key-value pair data structure. We have the added flexibility of using anything we please as our keys, alongside a clear set of a powerful methods that we have shown above. These methods allow us to be more semantic and straightforward about interacting and manipulating our data. It also avoids some of the potential pitfalls of the Object
, for example, because of the interface of a Map
, we can avoid the possibility of colliding with default Object
properties such as toString
or hasOwnProperty
. This is obviously an edge case but provides us with extra reassurance:
// With a traditional Object
const obj = {};
obj["toString"] = "woops";
console.log(obj.toString);
// Evaluates to woops
// With a Map
const map = Map();
map.get("toString"); // undefined
map.set("toString", "This is fine");
Overall it expresses a lot of cool properties that suite it better to behaviours we wanted from using an Object
as a Map
previously. Happy Mapping!
Published