Кратко
СкопированоMap
— коллекция для хранения данных любого типа в виде пар [ключ
, то есть каждое значение сохраняется по уникальному ключу, который потом используется для доступа к этому значению. Причём в качестве ключей тоже принимаются значения любого типа.
Основные методы для работы с коллекцией Map
:
set
— устанавливает значение;( ключ , значение ) get
— возвращает значение;( ключ ) has
— проверяет наличие переданного ключа;( ключ ) values
— возвращает итератор всех значений коллекции;( ) keys
— возвращает итератор всех ключей коллекции;( ) entries
— возвращает итератор пар( ) [ключ
;, значение ] delete
— удаляет конкретное значение;( ключ ) clear
— полностью очищает коллекцию;( ) for
— перебирает ключи и значения коллекции.Each ( колбэк )
Содержит свойство size
для получения количества значений в коллекции.
Пример
Скопированоconst someData = new Map()someData.set('1', 'Значение под строковым ключом 1')someData.set(1, 'Значение под числовым ключом 1')someData.set(true, 'Значение под булевым ключом true')console.log(someData.size)// 3console.log(someData.get(1))// Значение под числовым ключом 1console.log(someData.get('1'))// Значение под строковым ключом 1console.log(someData.has(true))// truesomeData.clear()console.log(someData.size)// 0
const someData = new Map() someData.set('1', 'Значение под строковым ключом 1') someData.set(1, 'Значение под числовым ключом 1') someData.set(true, 'Значение под булевым ключом true') console.log(someData.size) // 3 console.log(someData.get(1)) // Значение под числовым ключом 1 console.log(someData.get('1')) // Значение под строковым ключом 1 console.log(someData.has(true)) // true someData.clear() console.log(someData.size) // 0
Как понять
СкопированоСоздание коллекции
СкопированоКоллекция создаётся при помощи конструктора. Можно создать пустой Map
:
const map = new Map()console.log(map.size)// 0
const map = new Map() console.log(map.size) // 0
А можно сразу передать начальные значения. Для этого в конструктор нужно передать массив, состоящий из других массивов. Эти массивы должны состоять из двух элементов: первый элемент — ключ, а второй — значение:
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
Работа с коллекцией
СкопированоMap
предоставляет небольшой набор удобных методов для работы с данными.
Чтобы сохранить значение в коллекции, нужно использовать метод set
. Первым аргументом передаём ключ, а вторым — значение:
const map = new Map()map.set('js', 'JavaScript')
const map = new Map() map.set('js', 'JavaScript')
Получить значение можно при помощи метода get
. Единственным аргументом передаём ключ, данные которого хотим получить. Если в коллекции нет значения для переданного ключа, get
вернёт 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
Узнать, есть ли в коллекции значение с конкретным ключом, можно с помощью метода has
:
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
Удалять конкретное значение можно методом delete
, который также принимает ключ в качестве аргумента. delete
возвращает true
, если элемент для переданного ключа существовал и был удалён. Полностью очищает коллекцию метод clear
:
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
Обход значений
СкопированоMap
предоставляет встроенный итератор для обхода значений:
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
А ещё можно сделать то же самое при помощи метода for
:
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
При обходе значений Map
всегда выводит их в том порядке, в котором они были добавлены.
Отличия от объектов
СкопированоОбычные объекты тоже подходят для хранения данных. Однако ключи в них могут быть только строками или символами:
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
же позволяет использовать в качестве ключа любое значение: объект, функцию, примитивные значения и даже null
, undefined
и NaN
. Для сравнения ключей используется алгоритм SameValueZero.
Как работает алгоритм SameValueZero
Кратко. Алгоритм SameValueZero работает так же, как и строгое сравнение при помощи =
с единственным отличием: для SameValueZero NaN
равен NaN
. Именно по этой причине в качестве ключей Map
можно использовать NaN
— мы можем найти такой ключ простым сравнением.
Подробно. Алгоритм SameValueZero для сравнения переменных x
и y
согласно спецификации:
- Если типы
x
иy
отличаются, возвращаем false. Возможные типы:Undefined
,Null
,Boolean
,String
,Number
,Big
,Int Object
илиSymbol
. Не путать с результатом выполнения оператораtypeof
). - Если тип
x
иy
Number, то:- если значение
x
NaN и значениеy
NaN, возвращаем true; - если значение
x
-0, а значениеy
+0, возвращаем true; - если значение
x
+0, а значениеy
-0, возвращаем true; - возвращаем true, если значение
x
равно значениюy
, в противном случае возвращаем false.
- если значение
- Если тип
x
иy
Big
, возвращаем true, если значениеInt x
равно значениюy
. В противном случае возвращаем false. - Если тип
x
иy
Undefined
, возвращаем true. - Если тип
x
иy
Null
, возвращаем true. - Если тип
x
иy
String
, возвращаем true, еслиx
иy
одинаковые последовательности символов (одинаковая длина и такие же коды символов на соответствующих индексах). В противном случае возвращаем false. - Если тип
x
иy
Boolean
, возвращаем true, если оба значенияx
иy
true или оба значенияx
иy
false. В противном случае возвращаем false. - Если тип
x
иy
Symbol, возвращаем true, еслиx
иy
являются одним и тем же значением символа. В противном случае возвращаем false. - Если типы
x
иy
наследуются отObject
, возвращаем true, еслиx
иy
ссылаются на один и тот же объект. В противном случае возвращаем false.
const func = (name) => `Привет, ${name}`const obj = { foo: 'bar' }const map = new Map()map.set(func, 'значение func')map.set(obj, 'значение object')map.set(undefined, 'значение undefined')map.set(NaN, 'значение NaN')map.set(null, 'значение null')console.log(map.get(func))// значение funcconsole.log(map.get(obj))// значение objectconsole.log(map.get(undefined))// значение undefinedconsole.log(map.get(NaN))// значение NaNconsole.log(map.get(null))// значение null
const func = (name) => `Привет, ${name}` const obj = { foo: 'bar' } const map = new Map() map.set(func, 'значение func') map.set(obj, 'значение object') map.set(undefined, 'значение undefined') map.set(NaN, 'значение NaN') map.set(null, 'значение null') console.log(map.get(func)) // значение func console.log(map.get(obj)) // значение object console.log(map.get(undefined)) // значение undefined console.log(map.get(NaN)) // значение NaN console.log(map.get(null)) // значение null
При использовании SameValueZero для сравнения ключей, приведение типов не происходит. Поэтому число и строковое представление этого же числа будут являться двумя разными ключами:
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
При использовании непримитивных типов в качестве ключей стоит помнить, что они хранятся по ссылке, поэтому для доступа к заданному с помощью объекта ключу, необходимо передавать тот же самый объект.
Создадим две переменные, которые указывают на один и тот же объект, и добавим их ключами в Map
:
const dataObject = { position: 'left' }const sameObject = dataObjectconsole.log(dataObject === sameObject)// trueconst map = new Map()map.set(dataObject, 'Значение для dataObject')map.set(sameObject, 'Значение для sameObject')console.log(map.size)// 1console.log(map.get(dataObject))// Значение для sameObjectconsole.log(map.get(sameObject))// Значение для sameObject
const dataObject = { position: 'left' } const sameObject = dataObject console.log(dataObject === sameObject) // true const map = new Map() map.set(dataObject, 'Значение для dataObject') map.set(sameObject, 'Значение для sameObject') console.log(map.size) // 1 console.log(map.get(dataObject)) // Значение для sameObject console.log(map.get(sameObject)) // Значение для sameObject
А вот если мы возьмём два отдельных объекта с одинаковым содержимым, то мы получим два разных ключа:
const playerOne = { position: 'left' }const playerTwo = { position: 'left' }console.log(playerOne === playerTwo)// falseconst map = new Map()map.set(playerOne, 'Игрок 1')map.set(playerTwo, 'Игрок 2')console.log(map.size)// 2console.log(map.get(playerOne))// Игрок 1console.log(map.get(playerTwo))// Игрок 2
const playerOne = { position: 'left' } const playerTwo = { position: 'left' } console.log(playerOne === playerTwo) // false const map = new Map() map.set(playerOne, 'Игрок 1') map.set(playerTwo, 'Игрок 2') console.log(map.size) // 2 console.log(map.get(playerOne)) // Игрок 1 console.log(map.get(playerTwo)) // Игрок 2