DEV Community

Cover image for Do you know everything about Map in JavaScript?
Edvinas Daugirdas
Edvinas Daugirdas

Posted on

Do you know everything about Map in JavaScript?

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:

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
Enter fullscreen mode Exit fullscreen mode

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:

const pokemons = new Map([
  ['pikachu', { category: 'Mouse', type: 'Electric' }], 
  ['bulbasaur', { category: 'Seed', type: 'Grass' }]
])
Enter fullscreen mode Exit fullscreen mode

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.

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.

const obj = {}
obj[1] = 'probablyOne'
obj['1'] // 'probablyOne'
Enter fullscreen mode Exit fullscreen mode

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.

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'
Enter fullscreen mode Exit fullscreen mode

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.

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'
}
Enter fullscreen mode Exit fullscreen mode

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:

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

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

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

userFavPokemonMap.forEach((favPokemon, name) => {
    console.log(name)
})
Enter fullscreen mode Exit fullscreen mode

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.

userFavPokemonMap.set('John', { name: 'Pikachu', type: 'Electric' })
JSON.stringify() // "{}"
Enter fullscreen mode Exit fullscreen mode

You could create your own serialization and parsing if you wanted to of course, here’s how you can do it.

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.

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.

const numbersMap = new Map()

numbersMap.set(+0, 'nice tutorial')
numbersMap.get(0) // 'nice tutorial'
Enter fullscreen mode Exit fullscreen mode

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.

NaN === NaN // false

const nonNumbersMap = new Map()

nonNumbersMap.set(NaN, 'number?')
nonNumbersMap.get(NaN) // 'number?'
Enter fullscreen mode Exit fullscreen mode

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!

Latest comments (0)