Do you know everything about Map in JavaScript?
Tags: javascript
Published: 2021-06-28 17:37:54

2015 was a great year for JavaScript - the language received a much-awaited significant update, by the name of ECMAScript 6 (a.k.a. ES6, a.k.a. ECMAScript 2015 ¯\_(ツ)_/¯), the first update to the language since ES5 was standardized back in 2009. Among many features four newly formed data structures were introduced: `Map`, `Set`, `WeakMap`, `WeakSet`.

Surprisingly to me, six years have already passed since ES6 initial release and after all that time some of these data structures still feel so new and fresh. With all of that being said, feeling the pressure of my ever-growing impostor syndrome, I've decided to refresh my memory on one of these lovely structures  - `Map`. And if you're in the same boat as me (don't worry, there's nothing wrong with you) let's explore together what this thing can do.

## Same same, but different, but still the same

If you’ve been on the internet long enough you’ve probably encountered the meme before and it kind of relates to `Map` in a way. `Map` is quite similar to the well-known `Object` that you’ve been using for ages. So what is `Map` after all?

It’s a data structure that holds key-value pairs, just like our friend `Object`. Of course it has it’s fair share of differences, but the similarity is so apparent that historically `Object` has been used as `Map` (there were no other alternatives). Just look how readable and understandable this code snippet is when you have that mental model in your head:

```javascript
const pokemons = new Map()

pokemons.set('pikachu', { category: 'Mouse', type: 'Electric' })
pokemons.set('bulbasaur', { category: 'Seed', type: 'Grass' })

pokemons.get('pikachu') // { category: 'Mouse', type: 'Electric' }
pokemons.get('meowth') // undefined

pokemons.size // 2

pokemons.has('pikachu') // true
pokemons.delete('pikachu') // true
pokemons.has('pikachu') // false

pokemons.clear()
pokemons.size // 0
```

Sure, the API is different, but I’m pretty sure that you understand what this code does and what its purpose is by just looking at it. Essentially what we’re doing here is creating a new `Map` instance, setting some values, deleting them, checking the size, your standard stuff.

Instead of setting values as properties as we would on an `Object` (which you can also do on `Map`, but please don’t do that) we use this nifty API that `Map` gives us. This opens up some new capabilities like checking the size of an instance, like we did on line 9 with `pokemons.size`, which we can not do on an `Object` instance.

You could also initialize a `Map` with pre-existing values if you wanted to:

```javascript
const pokemons = new Map([
  ['pikachu', { category: 'Mouse', type: 'Electric' }], 
  ['bulbasaur', { category: 'Seed', type: 'Grass' }]
])
```

I’m not going to bore you by describing every method that exists on `Map`, but if you’re interested here’s a good place to start: [Map, Instance methods — JavaScript | MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map#instance_methods).

## But different…?

Now that we know what `Map` is and how it functions let’s explore the more interesting and impactful differences that it has compared to an `Object`.

### Key types and accidental keys

Although it make come as a surprise keys of an `Object` are always either a `String` or a `Symbol`. What does that mean for us? Well, for example, that means that the `Object` keys can not be a `Number`. In the following code snippet `obj[1]` key will be coerced to a `String`.

```javascript
const obj = {}
obj[1] = 'probablyOne'
obj['1'] // 'probablyOne'
```

That’s not the only limitation when it comes to keys in an `Object`, you might accidentally override a default `Object` key, like `toString` method for example. To be honest I can’t recall a situation where I would run into this particular “problem”, but I guess technically it could be an issue.

These problems don’t exist on a `Map`. It does not give a single flying duck what its key is. Want to give it a `Number` as a key? Yep.

Maybe a `Boolean`, `Function` or even an `Object`? No problem what so ever.

This type of functionality is quite useful when you’re not sure what type of keys you will be using. If the key is specified from an external source (say a user input or an API call response) `Map` is a good candidate to solve that problem. Or if you just want to use `Number`, `Function` or whatever type as a key instead of `String`, `Map` got you covered.

```javascript
const pagesSectionsMap = new Map()

pagesSectionsMap.set(1, 'Introduction')
pagesSectionsMap.set(50, 'Entering the shadow realm')

pagesSectionsMap.get(1) // 'Introduction'
pagesSectionsMap.get(50) // 'Entering the shadow realm'
```

### Order and iteration

`Object` is a nonordered data structure, meaning that it does not care about the sequence in which your key-value pairs were entered. Well, it actually does have an “order” now, but it’s hard to understand, there’s tons of rules and it’s just better not to rely on it, since the possibility of introducing a bug is relatively high.

It also does not implement an iteration protocol, meaning that objects are not iterable using `for...of` statement. You can get an iterable object using `Object.keys` or `Object.entries` though.

On the other hand `Map` is ordered, it remembers the original sequence of your key-value pairs and it also plays nicely with the iteration protocol. Cool. Let’s take a look how that might be useful.

```javascript
const userFavPokemonMap = new Map()

userFavPokemonMap.set('John', { name: 'Pikachu', type: 'Electric' })
userFavPokemonMap.set('Jane', { name: 'Bulbasaur', type: 'Grass' })
userFavPokemonMap.set('Tom', { name: 'Meowth', type: 'Normal' })

for ([user, favouritePokemon] of userFavPokemonMap) {
    console.log(user) // 'John', 'Jane', 'Tom'
}
```
Now you might be thinking: “Who cares what order these will be printed out?”. Little did you know that John and Jane are low-key maniacs and they like to be first everywhere. In all seriousness though maybe this is not the best example, but hopefully it conveys the concept. If anyone sees an obvious use case where order is important and it’s related to pokemons, let me know.

You could even use other methods that exist on `Map` and iterate through them in the same fashion:

```javascript
for (name of userFavPokemonMap.keys()) {
    console.log(name)// "John", "Jane", "Tom"
}

for (pokemon of userFavPokemonMap.values()) {
    console.log(pokemon) // { name: "Pikachu", type: "Electric" }, ..
}
```

You could even `forEach` this bad boy if you wanted to:

```javascript
userFavPokemonMap.forEach((favPokemon, name) => {
    console.log(name)
})
```

I want to reiterate that we could achieve almost the same functionality using a plain old `Object`, but if we care about the order of our values `Map` is definitely the way to go.

### Performance
`Map` has some distinct performance improvements when it comes to frequent additions and removals of key-value pairs unlike `Object`. If you ever find yourself in a position where you need to get some performance gains on that front `Map` just might be your new friend that comes to save the day.

### Serialization and parsing

This might be a bummer for some of you, because `Map` does not offer any serialization or parsing capabilities. That means that if we use `JSON.stringify` or `JSON.parse` we’ll not get much.

```javascript
userFavPokemonMap.set('John', { name: 'Pikachu', type: 'Electric' })
JSON.stringify() // "{}"
```

You could create your own serialization and parsing if you wanted to of course, [here’s how you can do it](https://stackoverflow.com/questions/29085197/how-do-you-json-stringify-an-es6-map).

### Key equality

`Map` uses a _SameValueZero_ algorithm. OK, but what does that mean? Let’s start by looking which equality algorithms currently exist in JavaScript:

- Abstract Equality Comparison (`==`)
- Strict Equality Comparison (`===`)
- SameValueZero (the one that `Map` uses)
- SameValue (`Object.is`)

I’m fairly sure that you’ve definitely encountered `==` or `===` in the wild. `Object.is` is something that I personally haven’t seen that often, it’s a bit out of topic, so in case you’re interested you can [read more here if you want](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is).

What we’re curious about is _SameValueZero_ and why it’s used in `Map` operations. To gain some instant familiarity just imagine that it’s the same as `===` only with some additional quirks.

_Quirk no. 1_: it treats signed zeroes as the same value. That means that `+0` and `-0` is the same in `Map` eyes.

```javascript
const numbersMap = new Map()

numbersMap.set(+0, 'nice tutorial')
numbersMap.get(0) // 'nice tutorial'
```

The only explanation that I could find why this is important is because `-0` could easily sneak into your code via an arithmetic operation, but you almost always want `-0` to be treated as `0`.

_Quirk no. 2_: it treats `NaN` as equal to other `NaN` values.

```javascript
NaN === NaN // false

const nonNumbersMap = new Map()

nonNumbersMap.set(NaN, 'number?')
nonNumbersMap.get(NaN) // 'number?'
```

This one is kind of straightforward, as we don’t want to have distinct `NaN` values.

---

That’s all folks. If you’ve made it until the end I just wanna say thanks, that really warms my heart ❤️

Until next time!