Кратко
Скопировано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иyNumber, то:- если значение
xNaN и значениеyNaN, возвращаем true; - если значение
x-0, а значениеy+0, возвращаем true; - если значение
x+0, а значениеy-0, возвращаем true; - возвращаем true, если значение
xравно значениюy, в противном случае возвращаем false.
- если значение
- Если тип
xиyBig, возвращаем true, если значениеInt xравно значениюy. В противном случае возвращаем false. - Если тип
xиyUndefined, возвращаем true. - Если тип
xиyNull, возвращаем true. - Если тип
xиyString, возвращаем true, еслиxиyодинаковые последовательности символов (одинаковая длина и такие же коды символов на соответствующих индексах). В противном случае возвращаем false. - Если тип
xиyBoolean, возвращаем true, если оба значенияxиytrue или оба значенияxиyfalse. В противном случае возвращаем false. - Если тип
xиySymbol, возвращаем 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