Кратко
Секция статьи "Кратко"Map
- коллекция для хранения данных любого типа в виде пар
, то есть каждое значение сохраняется по уникальному ключу, который потом используется для доступа к этому значению. Причём в качестве ключей тоже принимаются значения любого типа.
Основные методы для работы с коллекцией Map
:
set
— устанавливает значение;( ключ , значение ) get
— возвращает значение;( ключ ) has
— проверяет наличие переданного ключа;( ключ ) values
— возвращает итератор всех значений коллекции;( ) keys
— возвращает итератор всех ключей коллекции;( ) entries
— возвращает итератор пар( )
;[ ключ , значение ] delete
— удаляет конкретное значение;( ключ ) clear
— полностью очищает коллекцию;( ) forEach
— перебирает ключи и значения коллекции.( колбэк )
Содержит свойство 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
А ещё можно сделать то же самое при помощи метода forEach
:
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, BigInt, 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
BigInt, то возвращаем true, если значение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) => `Hello, ${name}`const obj = { foo: 'bar' }const map = new Map()map.set(func, 'func value')map.set(obj, 'object value')map.set(undefined, 'undefined value')map.set(NaN, 'NaN value')map.set(null, 'null value')console.log(map.get(func))// func valueconsole.log(map.get(obj))// object valueconsole.log(map.get(undefined))// undefined valueconsole.log(map.get(NaN))// NaN valueconsole.log(map.get(null))// null value
const func = (name) => `Hello, ${name}` const obj = { foo: 'bar' } const map = new Map() map.set(func, 'func value') map.set(obj, 'object value') map.set(undefined, 'undefined value') map.set(NaN, 'NaN value') map.set(null, 'null value') console.log(map.get(func)) // func value console.log(map.get(obj)) // object value console.log(map.get(undefined)) // undefined value console.log(map.get(NaN)) // NaN value console.log(map.get(null)) // null value
При использовании 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, '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
А вот если мы возьмём два отдельных объекта с одинаковым содержимым, то мы получим два разных ключа:
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