Maps in Javascript ES6
Tags: javascript
Published: 2018-07-17 19:11:13

---
title: Maps in Javascript ES6
published: true
description: A look at the new Map and WeakMap in ES6
cover_image: https://cdn-images-1.medium.com/max/640/1*SL4sWHdjGR3vo0x5ta3xfw.jpeg
tags: javascript
---

## Introduction

ES6 introduced a lot of new things. I've already write about <a href="https://dev.to/damcosset/sets-in-javascript-es6-3fdh" target="_blank">*Sets*</a>, so today we'll explore *Maps*. What are those? A *Map* is an unordered list of key-values pairs where the key **AND** the value can be of any type. 

## Problems solved

Developers have tried to implement maps before ES6, but some issues arise because of the way object properties are handle in Javascript. In an object, every property must be a string. So, if you give an object a key with a different type, it will be coerced into a string.


```javascript
let map = {}

map[5] = 4
map[{}] = 'An object'

// { '5': 4, '[object Object]': 'An object' }

```

As you can see, our 5 became '5', our empty object became '[object Object]'. That's some serious limitations there!

In ES6, Maps use the Object.is() method to compare keys, just like Sets do with their values. Maps also do not make every key a string, every type is allowed.

```javascript

Object.is(5, '5') // false
Object.is({}, {}) // false
```

## Constructor

So, how to create a new Map? By using `new Map()`. You can also initialize a map with an array of arrays:

```javascript
const map = new Map()
// Map {}

const map = new Map([[5, 42], ["name", "Paul"], ["age", 45]])
// Map { 5 => 42, 'name' => 'Paul', 'age' => 45 }
```

In the array of arrays, each array represents a key-value pair. The first item in each array will become the key, the second will be the value. The structure may look odd, but it is the best way to make sure we can allow any type of data for keys.

## Maps methods

To interact with a map, you have a few methods at your disposal. 

- The `set(key, value)` method adds a pair to the map. 
- The `get(key)` method retrieves a value from the map. The `get` method will return `undefined` if nothing has been found. 
- The `has(key)` method checks if the key exists in the map. Returns true or false.
- The `delete(key)` method removes the key and its value from the map.
- The `clear()` method removes all keys and values from the map.
- Finally, maps have a `size` property that returns the number of key/value pairs in the map.

```javascript
const map = new Map()

map.set(5, "Hello")
map.set("5", "World")
map.set("John", "The revelator")
map.size // 3
// Map { 5 => 'Hello', '5' => 'World', 'John' => 'The revelator' }

map.get(5) // Hello
map.has('5') // true
map.get('Random') // undefined
map.has('John') // true

map.delete('5')
map.size // 2
// Map { 5 => 'Hello', 'John' => 'The revelator' }

map.clear()
map.size // 0
// Map {}
```

## Objects keys in map

As I mentioned earlier, objects can be used as keys in a map.

```javascript
const map = new Map()
let obj1 = {}
let obj2 = {}

map.set(obj1, 12)
map.set(obj2, "OBJECT")
map.size // 2
// Map { {} => 12, {} => 'OBJECT' }

```

As you can see, even though we are are using two empty objects as keys, we are using the *references* of those objects in the map. Therefore, Object.is(), who is used for comparing the keys, returns false. Again, notice that the object are not coerced into strings.

## Iteration

You can iterate through a Map by using forEach(). The callback passed receives three arguments: the value, the key and the map we are using.

```javascript
const map = new Map([[5, 42], ["name", "Paul"], ["age", 45]])

map.forEach((value, key, thisMap) => {
	console.log(`${key} => ${value}`)
	console.log(thisMap === map)
})

//5 => 42
//true

//name => Paul
//true

//age => 45
//true
```

## Weak Maps

Weak maps obey the same principle of weak sets. In a Weak Map, every **key** must be an object. Weak maps are used to store weak object references. What does that mean?

```javascript
const map = new Map()
let obj1 = {}
map.set(obj1, 12)
//Map { {} => 12 }
obj1 = null // I remove the obj1 reference
// Map { {} => 12 } // But the reference still exists in the map anyway
```

In this case, our object's reference still exists in the map. Removing the reference everywhere else does not remove it from the map. It is not garbage collected to free memory. In certain cases, you would want to optimize memory usage and avoid memory leaks. This is what a WeakMap does for you. If the reference of an object disappear everywhere else in your program, it will be removed from the WeakSet also.

```javascript
const map = new WeakMap()

let obj = {} // creates a reference to obj
map.set(obj, 12) // stores the reference inside the WeakMap as a key
map.has(obj) // true
map.get(obj) // 12

obj = null /* removes the reference. Will also remove it from the WeakMap because there are no other references to this object */

map.has(obj) // false
map.get(obj) // undefined
console.log(map) // WeakMap {}

// obj is gone from the WeakMap

```

**Note**: This only works when objects are stored as **keys**, not values. If an object is stored as a value and all other references disappear, it will not disappear from the WeakMap. Weak map keys are weak references, not weak map values.

You can also initialize a WeakMap with an array of arrays, just like a Map. The difference is that because every key must be an object, the first item in each array must be an object. An error will be thrown if you try to put a nonobject key inside a WeakMap.

**Note**: WeakMap does not have a *size* property

## Weak Maps use cases

One possible use case for WeakMap could be when you are tracking DOM elements. By using a WeakMap, you could store DOM elements as keys. As soon as the element is removed, the object will be garbage collected to free memory.

```javascript
const map = new WeakMap()
const element = document.querySelector(".button")

map.set(element, "Buttons")

map.get(element) // "Buttons"

element.parentNode.removeChild(element) // remove the element
element = null // removes reference

// WeakMap now empty!
```

One other practical use of WeakMap is to store private object data. All object properties are public in ES6. So how would you go about it? In ES5, you could do something like this:

```javascript

var Car = (function(){

	var privateCarsData = {}
	var privateId = 0

	function Car(name, color){
		Object.defineProperty(this, "_id", {value: privateId++})

		privateCarsData[this._id] = {
			name: name,
			color: color
		}
	}

	Car.prototype.getCarName = function(){
		return privateCarsData[this._id].name
	}

	Car.prototype.getCarColor = function(){
		return privateCarsData[this._id].color
	}

	return Car
}())
```

This is as close as you'll get to have truly private data in ES5. Here, the Car definition is wrapped inside an Immediately Invoked Function Expression (IIFE). We have two private variables, privateCarsData and privateId. privateCarsData stores private information for each Car instance and privateId generates a unique id for each instance.

When we call `Car(name, color)`, the _id property is added inside privateCarsData and this receives an object with name and color properties. *getCarName* and *getCarColor* retrieve data by using this._id as the key.

The data is safe because privateCarsData is not accessible outside the IIFE, this._id is exposed however. The problem is that there are no ways to know when a Car instance is destroyed. Therefore, we can't update privateCarsData appropriately when an instance disappear and it will always contain extra data.

```javascript
const Car = (function(){

	const privateCarsData = new WeakMap()

	function Car(name, color){
		// this => Car instance
		privateCarsData.set(this, {name, color})
	}

	Car.prototype.getCarName = function(){
		return privateCarsData.get(this).name
	}

	Car.prototype.getCarColor = function(){
		return privateCarsData.get(this).color
	}

	return Car
}())
```

This version uses a WeakMap for the privateCarsData instead of an object. We will use the Car instance as the key, so we don't need to generate a unique id for each instance. The key will be `this` and the value is an object containing the name and the color. *getCarName* and *getCarColor* retrieve the values by passing `this` to the get method. And now, whenever a Car instance is destroyed, the key referencing that instance inside the privateCarsData will be garbage collected to free memory.


## Conclusion

Anytime you will want to use only object keys, weak maps will be your best choice. Memory will be optimised and memory leaks avoided. However, weak maps give you very little visibility into what they have. You can't use *forEach()*, no *size* property and no *clear()* method. If you need to inspect the contents, use regular maps. Obviously, if you want to use nonobject keys, you will have to use regular maps too.