Map

Collection for storing data by key.

Time to read: 8 min

Briefly

Map — a collection for storing data of any type in the form of pairs [key, value], meaning each value is stored with a unique key that is then used to access that value. Moreover, values of any type are also accepted as keys.

Main methods for working with the Map collection:

  • set(key, value) — sets the value;
  • get(key) — returns the value;
  • has(key) — checks for the presence of the provided key;
  • values() — returns an iterator of all values in the collection;
  • keys() — returns an iterator of all keys in the collection;
  • entries() — returns an iterator of pairs [key, value];
  • delete(key) — removes a specific value;
  • clear() — completely clears the collection;
  • forEach(callback) — iterates over the keys and values of the collection.

Contains the size property to get the number of values in the collection.

Example

        
          
          const someData = new Map()someData.set('1', 'Value under string key 1')someData.set(1, 'Value under numeric key 1')someData.set(true, 'Value under boolean key true')console.log(someData.size)// 3console.log(someData.get(1))// Value under numeric key 1console.log(someData.get('1'))// Value under string key 1console.log(someData.has(true))// truesomeData.clear()console.log(someData.size)// 0
          const someData = new Map()

someData.set('1', 'Value under string key 1')
someData.set(1, 'Value under numeric key 1')
someData.set(true, 'Value under boolean key true')

console.log(someData.size)
// 3

console.log(someData.get(1))
// Value under numeric key 1

console.log(someData.get('1'))
// Value under string key 1

console.log(someData.has(true))
// true

someData.clear()

console.log(someData.size)
// 0

        
        
          
        
      

How to Understand

Creating a Collection

A collection is created using the constructor. You can create an empty Map:

        
          
          const map = new Map()console.log(map.size)// 0
          const map = new Map()

console.log(map.size)
// 0

        
        
          
        
      

Or you can immediately pass initial values. For this, an array consisting of other arrays must be passed to the constructor. These arrays should consist of two elements: the first element — the key, and the second — the value:

        
          
          const map = new Map([['js', 'JavaScript'], ['css', 'Cascading Style Sheets']])console.log(map.size)// 2console.log(map.get('js'))// JavaScript
          const map = new Map([['js', 'JavaScript'], ['css', 'Cascading Style Sheets']])

console.log(map.size)
// 2

console.log(map.get('js'))
// JavaScript

        
        
          
        
      

Working with the Collection

Map provides a small set of convenient methods for working with data.

To store a value in the collection, you need to use the set() method. The first argument is the key, and the second is the value:

        
          
          const map = new Map()map.set('js', 'JavaScript')
          const map = new Map()

map.set('js', 'JavaScript')

        
        
          
        
      

You can retrieve a value using the get() method. The only argument is the key whose data you want to get. If there is no value in the collection for the provided key, get() will return undefined.

        
          
          const map = new Map()map.set('js', 'JavaScript')console.log(map.get('js'))// JavaScript
          const map = new Map()
map.set('js', 'JavaScript')

console.log(map.get('js'))
// JavaScript

        
        
          
        
      

You can check if there is a value in the collection with a specific key using the has() method:

        
          
          const map = new Map()map.set('js', 'JavaScript')console.log(map.has('js'))// trueconsole.log(map.has('css'))// false
          const map = new Map()
map.set('js', 'JavaScript')

console.log(map.has('js'))
// true

console.log(map.has('css'))
// false

        
        
          
        
      

You can remove a specific value using the delete() method, which also accepts the key as an argument. delete() returns true if the element for the provided key existed and was removed. The clear() method completely clears the collection:

        
          
          const map = new Map()map.set('html', 'HTML')map.set('css', 'CSS')map.set('js', 'JavaScript')console.log(map.size)// 3map.delete('css')console.log(map.size)// 2map.clear()console.log(map.size)// 0
          const map = new Map()

map.set('html', 'HTML')
map.set('css', 'CSS')
map.set('js', 'JavaScript')

console.log(map.size)
// 3

map.delete('css')

console.log(map.size)
// 2

map.clear()

console.log(map.size)
// 0

        
        
          
        
      

Iterating Over Values

Map provides a built-in iterator for iterating over values:

        
          
          const map = new Map()map.set('html', 'HTML')map.set('css', 'CSS')map.set('js', 'JavaScript')for (let [key, value] of map) {  console.log(`${key} — ${value}`)}// html — HTML// css — CSS// js — JavaScript
          const map = new Map()

map.set('html', 'HTML')
map.set('css', 'CSS')
map.set('js', 'JavaScript')

for (let [key, value] of map) {
  console.log(`${key}${value}`)
}
// html — HTML
// css — CSS
// js — JavaScript

        
        
          
        
      

You can also do the same using the forEach() method:

        
          
          const map = new Map()map.set('html', 'HTML')map.set('css', 'CSS')map.set('js', 'JavaScript')map.forEach((value, key) => {  console.log(`${key} — ${value}`)})// html — HTML// css — CSS// js — JavaScript
          const map = new Map()

map.set('html', 'HTML')
map.set('css', 'CSS')
map.set('js', 'JavaScript')

map.forEach((value, key) => {
  console.log(`${key}${value}`)
})
// html — HTML
// css — CSS
// js — JavaScript

        
        
          
        
      

When iterating over values, Map always outputs them in the order they were added.

Differences from Objects

Regular objects can also be used for storing data. However, keys in them can only be strings or symbols:

        
          
          const obj = {  1: 'String',  '2': 'Number',  true:'Bool',}console.log(Object.keys(obj))// [ '1', '2', 'true' ]
          const obj = {
  1: 'String',
  '2': 'Number',
  true:'Bool',
}

console.log(Object.keys(obj))
// [ '1', '2', 'true' ]

        
        
          
        
      

Map, on the other hand, allows any value to be used as a key: an object, function, primitive values, and even null, undefined and NaN. The SameValueZero algorithm is used to compare keys.

How the SameValueZero Algorithm Works

Briefly. The SameValueZero algorithm works like strict comparison using === with the single distinction: for SameValueZero, NaN equals NaN. This is why NaN can be used as keys in Map — we can find such a key through simple comparison.

Details. The SameValueZero algorithm for comparing variables x and y according to the specification:

  1. If the types of x and y differ, return false. Possible types: Undefined, Null, Boolean, String, Number, BigInt, Object or Symbol. Do not confuse with the result of the typeof operator.
  2. If the type of x and y is Number:
    • if the value of x is NaN and the value of y is NaN, return true;
    • if the value of x is -0 and the value of y is +0, return true;
    • if the value of x is +0 and the value of y is -0, return true;
    • return true if the value of x is equal to the value of y, otherwise return false.
  3. If the type of x and y is BigInt, return true if the value of x equals the value of y. Otherwise, return false.
  4. If the type of x and y is Undefined, return true.
  5. If the type of x and y is Null, return true.
  6. If the type of x and y is String, return true if x and y are the same sequences of characters (same length and the same character codes at corresponding indexes). Otherwise, return false.
  7. If the type of x and y is Boolean, return true if both values of x and y are true or both values of x and y are false. Otherwise, return false.
  8. If the type of x and y is Symbol, return true if x and y are the same symbol. Otherwise, return false.
  9. If the types of x and y inherit from Object, return true if x and y reference the same object. Otherwise, return false.
        
          
          const func = (name) => `Hello, ${name}`const obj = { foo: 'bar' }const map = new Map()map.set(func, 'value func')map.set(obj, 'value object')map.set(undefined, 'value undefined')map.set(NaN, 'value NaN')map.set(null, 'value null')console.log(map.get(func))// value funcconsole.log(map.get(obj))// value objectconsole.log(map.get(undefined))// value undefinedconsole.log(map.get(NaN))// value NaNconsole.log(map.get(null))// value null
          const func = (name) => `Hello, ${name}`
const obj = { foo: 'bar' }

const map = new Map()
map.set(func, 'value func')
map.set(obj, 'value object')
map.set(undefined, 'value undefined')
map.set(NaN, 'value NaN')
map.set(null, 'value null')

console.log(map.get(func))
// value func

console.log(map.get(obj))
// value object

console.log(map.get(undefined))
// value undefined

console.log(map.get(NaN))
// value NaN

console.log(map.get(null))
// value null

        
        
          
        
      

When using SameValueZero to compare keys, type coercion does not occur. Therefore, a number and its string representation will be two different keys:

        
          
          const map = new Map()map.set(1, 'numeric 1')map.set('1', 'string 1')console.log(map.size)// 2console.log(map.get(1))// numeric 1console.log(map.get('1'))// string 1
          const map = new Map()

map.set(1, 'numeric 1')
map.set('1', 'string 1')

console.log(map.size)
// 2

console.log(map.get(1))
// numeric 1

console.log(map.get('1'))
// string 1

        
        
          
        
      

When using non-primitive types as keys, remember that they are stored by reference, so to access the key set with an object, you need to pass the same object.

Let’s create two variables that point to the same object, and use them as keys in Map:

        
          
          const dataObject = { position: 'left' }const sameObject = dataObjectconsole.log(dataObject === sameObject)// trueconst map = new Map()map.set(dataObject, 'Value for dataObject')map.set(sameObject, 'Value for sameObject')console.log(map.size)// 1console.log(map.get(dataObject))// Value for sameObjectconsole.log(map.get(sameObject))// Value for sameObject
          const dataObject = { position: 'left' }
const sameObject = dataObject

console.log(dataObject === sameObject)
// true

const map = new Map()
map.set(dataObject, 'Value for dataObject')
map.set(sameObject, 'Value for sameObject')

console.log(map.size)
// 1

console.log(map.get(dataObject))
// Value for sameObject

console.log(map.get(sameObject))
// Value for sameObject

        
        
          
        
      

However, if we take two separate objects with the same content, we will get two different keys:

        
          
          const playerOne = { position: 'left' }const playerTwo = { position: 'left' }console.log(playerOne === playerTwo)// falseconst map = new Map()map.set(playerOne, 'Player 1')map.set(playerTwo, 'Player 2')console.log(map.size)// 2console.log(map.get(playerOne))// Player 1console.log(map.get(playerTwo))// Player 2
          const playerOne = { position: 'left' }
const playerTwo = { position: 'left' }

console.log(playerOne === playerTwo)
// false

const map = new Map()
map.set(playerOne, 'Player 1')
map.set(playerTwo, 'Player 2')

console.log(map.size)
// 2

console.log(map.get(playerOne))
// Player 1

console.log(map.get(playerTwo))
// Player 2