Map

Об'єкт Map (відображення) містить пари ключ-значення і запам'ятовує порядок вставки ключів у об'єкт. Будь-які значення (і об'єкти, і примітивні значення) можуть використовуватись і як ключ, і як значення.

Спробуйте його в дії

Опис

Об'єкти Map є колекціями пар ключ-значення. Конкретний ключ в Map може зустрітись лише раз; він неповторний в межах колекції Map. Обхід об'єкта Map відбувається за його парами ключ-значення: цикл for...of на кожній ітерації поверне масив із двох елементів, [ключ, значення]. Обхід відбувається в порядку додання, котрий відповідає порядкові, в якому кожна пара ключ-значення була вставлена у відображення методом set() (тобто коли до виклику set() у відображенні не було ключа з таким само значенням).

Специфікація вимагає, щоб відображення були реалізовані "так, щоб в середньому час доступу був сублінійним відносно числа елементів колекції". Таким чином, внутрішньо вони можуть бути представлені як геш-таблиця (з доступом O(1)), як дерево пошуку (з доступом O(log(N))) або будь-яка інша структура даних, поки складність доступу краща за O(N).

Рівність ключів

Рівність значень заснована на алгоритмі sameValueZero. (Раніше використовувався алгоритм SameValue, котрий розглядав 0 і -0 як різні значення. Перевірте сумісність із браузерами.) Це означає, що значення NaN вважається рівним іншому значенню NaN (попри те, що NaN !== NaN), а всі решта значень перевіряються на рівність згідно з семантикою оператора ===.

Порівняння Object та Map

Object дуже схожий до Map: обидві структури даних дають змогу задавати значення за ключами, отримувати ці значення, видаляти ключі та визначати, чи зберігається щось за певним ключем. З таких міркувань (а ще тому, що не існувало вбудованих альтернатив), історично Object використовується як Map.

Однак, є важливі відмінності, котрі роблять Map бажаним у деяких випадках:

`Map` `Object`
Випадкові ключі Новостворений Map усталено не містить ніяких ключів. Він містить лише те, що було явно покладено в нього.

Object має прототип, а тому він містить певні усталені ключі, котрі можуть конфліктувати з вашими, якщо ви недостатньо обережні.

Примітка: Цю проблему можна обійти за допомогою Object.create(null), хоча такий підхід рідко застосовується.

Безпека Об'єкти Map безпечно використовувати з наданими користувачем ключами та значеннями.

Задання наданих користувачем пар ключ-значення на об'єкті Object може дати нападникові змогу замістити прототип об'єкта, що може призвести до атак об'єктних ін'єкцій . Подібно до проблеми конфліктних ключів, ситуацію тут можна поліпшити шляхом використання об'єктів з прототипом null.

Типи ключів Ключі в Map можуть мати будь-яке значення (включно з функціями, об'єктами та будь-якими примітивними значеннями). Ключі в Object повинні мати тип або String, або Symbol.
Порядок ключів

Ключі в Map впорядковані простим і прямолінійним чином, а саме: об'єкт Map перебирає записи, ключі та значення в порядку, в якому вони були вставлені.

Хоча ключі звичайного Object наразі впорядковані, це не завжди було так, та й сам порядок складний. Як наслідок, краще не опиратися на порядок властивостей в об'єктах.

Цей порядок було вперше визначено лише для власних властивостей в ECMAScript 2015; ECMAScript 2020 визначає також порядок успадкованих властивостей. Проте зауважте, що жоден механізм не дозволяє перебрати всі властивості об'єкта; різні механізми охоплюють різні підмножини властивостей. Зокрема, (for-in охоплює лише перелічувані властивості з рядковими ключами; Object.keys враховує лише власні перелічувані властивості з рядковими ключами; Object.getOwnPropertyNames включає власні властивості з рядковими ключами, навіть неперелічувані; Object.getOwnPropertySymbols робить те саме, але лише для властивостей з ключами-символами, і т.д.)

Розмір

Кількість елементів всередині Map легко отримується з його властивості size. З'ясування кількості елементів усередині Object виконується в обхід, менш ефективно. Загальноприйнятий спосіб це зробити – через length масиву, поверненого з Object.keys().
Перебирання Map — це ітерований об'єкт, тож його елементи можна перебирати напряму.

Object не реалізовує протокол ітерації, тому поля об'єктів типово не можна перебирати напряму JavaScript-інструкцією for...of.

Зауваження:

  • Об'єкт може реалізовувати протокол ітерації, а ще можна отримати ітероване значення для об'єкта за допомогою Object.keys чи Object.entries.
  • Інструкція for...in дає змогу перебирати лише перелічувані властивості об'єкта.
Швидкодія

Краще працює при ситуаціях, коли необхідно часто додавати й видаляти пари ключ-значення.

Не оптимізований для частого вставляння і видалення пар ключ-значення.

Серіалізація і парсинг

Не має нативної підтримки для серіалізації чи парсингу.

(Проте можна збудувати власну підтримку для серіалізації та розбирання об'єктів Map за допомогою JSON.stringify() з його аргументом replacer, і JSON.parse() з його аргументом reviver. Дивіться запитання на Stack Overflow Як застосувати JSON.stringify до ES6 Map? (англ.)).

Нативна підтримка серіалізації з Object у JSON за допомогою JSON.stringify().

Нативна підтримка для парсингу JSON у Object за допомогою JSON.parse().

Призначення властивостей об'єкта

Призначення властивостей об'єкта так само працює і для об'єктів типу Map, і це може спричинити суттєву плутанину.

Отже, може скластися враження, що це працює таким чином:

const wrongMap = new Map();
wrongMap["bla"] = "blaa";
wrongMap["bla2"] = "blaaa2";

console.log(wrongMap); // Map { bla: 'blaa', bla2: 'blaaa2' }

Однак цей спосіб присвоєння властивості ніяк не взаємодіє зі структурою даних Map. Він лише використовує особливість звичайного об'єкта. Значення 'bla' не збереглося всередині Map для запитів. Інші операції на даних зазнають невдачі:

wrongMap.has("bla"); // false
wrongMap.delete("bla"); // false
console.log(wrongMap); // Map { bla: 'blaa', bla2: 'blaaa2' }

Правильно використовувати Map для зберігання даних через метод set(key, value).

const contacts = new Map();
contacts.set("Яся", { phone: "213-555-1234", address: "123 N 1st Ave" });
contacts.has("Яся"); // true
contacts.get("Галина"); // undefined
contacts.set("Галина", { phone: "617-555-4321", address: "321 S 2nd St" });
contacts.get("Яся"); // {phone: "213-555-1234", address: "123 N 1st Ave"}
contacts.delete("Роман"); // false
contacts.delete("Яся"); // true
console.log(contacts.size); // 1

Map-подібні браузерні API

Браузерні Map-подібні об'єкти – це інтерфейси Web API, які багато в чому поводяться подібно до Map.

Як і Map, їхні записи можуть бути ітеровані в тому самому порядку, в якому були додані до об'єкта. Також Map-подібні об'єкти та Map мають властивості та методи, що поділяють однакові назву та поведінку. Проте, на відміну від Map, ці об'єкти дозволяють лише певні попередньо визначені типи для ключів та значень кожного запису.

Дозволені типи задані у визначенні специфікації IDL. Наприклад, RTCStatsReport – це Map-подібний об'єкт, який повинен використовувати рядки як ключі та об'єкти як значення. Це визначено в специфікації IDL нижче:

interface RTCStatsReport {
  readonly maplike<DOMString, object>;
};

Map-подібні об'єкти – це або лише для читання, або для читання та запису (див. ключове слово readonly у специфікації IDL вище).

Ці методи та властивості мають таку ж поведінку, як рівносильні сутності в Map, окрім обмеження на типи ключів та значень.

Нижче – приклади Map-подібних браузерних об'єктів лише для читання:

Конструктор

Map()

Створює новий об'єкт Map.

Статичні властивості

Map[@@species]

Функція конструктора, що застосовується для створення похідних об'єктів.

Статичні методи

Map.groupBy()

Групує елементи переданого ітерованого об'єкта за допомогою значень, повернених переданою функцією зворотного виклику. Результівний об'єкт Map використовує унікальні значення з перевіркової функції як ключі, котрі можуть вживатися для отримання масиву елементів кожної групи.

Властивості примірника

Ці властивості означені на Map.prototype і є спільними для всіх примірників Map.

Map.prototype.constructor

Функція-конструктор, що створила об'єкт-примірник. Для примірників Map початковим значенням є конструктор Map.

Map.prototype[@@toStringTag]

Початкове значення властивості @@toStringTag – рядок "Map". Ця властивість використовується в Object.prototype.toString().

Map.prototype.size

Повертає кількість пар ключ-значення об'єкта Map.

Map.prototype[@@toStringTag]

Початковим значенням властивості @@toStringTag є рядок "Map". Вона використовується в Object.prototype.toString().

Методи примірника

Map.prototype.clear()

Видаляє всі пари ключ-значення з об'єкта Map.

Map.prototype.delete()

Повертає true, якщо вказаний елемент знаходився всередині Map і був успішно видалений, або ж false, якщо вказаний елемент не існує. Після цього map.has(key) повертатиме false.

Map.prototype.entries()

Повертає новий об'єкт Iterator, що містить масив із двох елементів: [key, value], на кожний елемент об'єкта Map, у порядку їх вставки.

Map.prototype.forEach()

Викликає функцію callbackFn один раз для кожної пари ключ-значення, наявної в об'єкті Map, в порядку вставки. Якщо в метод forEach передано параметр thisArg, його буде використано як значення this для кожної функції зворотного виклику.

Map.prototype.get()

Повертає значення, пов'язане з key, або ж undefined, якщо такого значення немає.

Map.prototype.has()

Повертає булеве значення, яке відображає наявність чи відсутність якогось значення, пов'язаного з переданим ключем, в об'єкті Map.

Map.prototype.keys()

Повертає новий об'єкт Iterator, що містить ключі кожного елементу об'єкта Map, в порядку їх вставки.

Map.prototype.set()

Призначає значення за переданим ключем в об'єкті Map. Повертає об'єкт Map.

Map.prototype.values()

Повертає новий об'єкт Iterator, який містить значення кожного елементу об'єкта Map, в порядку їх вставки.

Map.prototype[@@iterator]()

Повертає новий об'єкт Iterator, що містить масив із двох елементів: [key, value], на кожний елемент об'єкта Map, у порядку їх вставки.

Приклади

Застосування об'єкта Map

const myMap = new Map();

const keyString = "рядок";
const keyObj = {};
const keyFunc = function () {};

// встановлення значень
myMap.set(keyString, "значення, асоційоване з рядком");
myMap.set(keyObj, "значення, асоційоване з об'єктом");
myMap.set(keyFunc, "значення, асоційоване з функцією");

console.log(myMap.size); // 3

// отримання значень
console.log(myMap.get(keyString)); // "значення, асоційоване з рядком"
console.log(myMap.get(keyObj)); // "значення, асоційоване з об\'єктом"
console.log(myMap.get(keyFunc)); // "значення, асоційоване з функцією"

console.log(myMap.get("рядок")); // "значення, асоційоване з рядком"
// оскільки keyString === 'a string'
console.log(myMap.get({})); // undefined, оскільки keyObj !== {}
console.log(myMap.get(function () {})); // undefined, оскільки keyFunc !== function () {}

Застосування NaN як ключів для Map

Значення NaN також можна використовувати як ключ. І хоча кожне значення NaN не дорівнює саме собі (вираз NaN !== NaN — істинний), наступний приклад спрацює, тому що значення NaN неможливо розрізнити одне від одного:

const myMap = new Map();
myMap.set(NaN, "не число");

myMap.get(NaN);
// "не число"

const otherNaN = Number("foo");
myMap.get(otherNaN);
// "не число"

Перебирання об'єктів Map за допомогою for...of

Об'єкти Map можна перебирати за допомогою циклу for...of:

const myMap = new Map();
myMap.set(0, "нуль");
myMap.set(1, "один");

for (const [key, value] of myMap) {
  console.log(`${key} = ${value}`);
}
// 0 = нуль
// 1 = один

for (const key of myMap.keys()) {
  console.log(key);
}
// 0
// 1

for (const value of myMap.values()) {
  console.log(value);
}
// нуль
// один

for (const [key, value] of myMap.entries()) {
  console.log(`${key} = ${value}`);
}
// 0 = нуль
// 1 = один

Перебирання об'єктів Map за допомогою forEach()

Об'єкти Map можна перебирати за допомогою методу forEach():

myMap.forEach((value, key) => {
  console.log(`${key} = ${value}`);
});
// 0 = нуль
// 1 = один

Зв'язок з масивами

const kvArray = [
  ["key1", "value1"],
  ["key2", "value2"],
];

// Використаємо звичайний конструктор Map, щоб перетворити двовимірний масив комбінацій ключ-значення на `Map`
const myMap = new Map(kvArray);

console.log(myMap.get("key1")); // повертає "value1"

// Використаємо Array.from(), щоб перетворити об'єкт `Map` у двовимірний масив пар ключ-значення
console.log(Array.from(myMap)); // Покаже точнісінько такий самий масив, як kvArray

// Лаконічний спосіб зробити те саме, за допомогою оператора розгортання
console.log([...myMap]);

// Або ж використовуйте ітератори keys() чи values() і перетворюйте їх у масив
console.log(Array.from(myMap.keys())); // ["key1", "key2"]

Клонування і злиття об'єктів Map

Так само як і масиви, об'єкти Map можна клонувати:

const original = new Map([[1, "one"]]);

const clone = new Map(original);

console.log(clone.get(1)); // one
console.log(original === clone); // false (корисно для поверхневого порівняння)

Зауваження: Майте на увазі, що власне дані тут клоновано не було.

Об'єкти Map можна поєднувати, зберігаючи унікальність ключів:

const first = new Map([
  [1, "one"],
  [2, "two"],
  [3, "three"],
]);

const second = new Map([
  [1, "uno"],
  [2, "dos"],
]);

// Зливаємо докупи два об'єкти `Map`. У разі конфлікту ключів наступний перезапише попередній.
// Синтаксис розгортання фактично перетворює `Map` на масив
const merged = new Map([...first, ...second]);

console.log(merged.get(1)); // uno
console.log(merged.get(2)); // dos
console.log(merged.get(3)); // three

Об'єкти Map можна також об'єднувати з іншими масивами:

const first = new Map([
  [1, "one"],
  [2, "two"],
  [3, "three"],
]);

const second = new Map([
  [1, "uno"],
  [2, "dos"],
]);

// Зливаємо докупи об'єкти `Map` з масивом. У разі конфлікту ключів наступний перезапише попередній.
const merged = new Map([...first, ...second, [1, "eins"]]);

console.log(merged.get(1)); // eins
console.log(merged.get(2)); // dos
console.log(merged.get(3)); // three

Специфікації

Сумісність із браузерами

desktop mobile server
Chrome Edge Firefox Internet Explorer Opera Safari WebView Android Chrome Android Firefox for Android Opera Android Safari on iOS Samsung Internet Deno Node.js
Map
Chrome Full support 38
Edge Full support 12
Firefox Full support 13
Internet Explorer Full support 11
Opera Full support 25
Safari Full support 8
WebView Android Full support 38
Chrome Android Full support 38
Firefox for Android Full support 14
Opera Android Full support 25
Safari on iOS Full support 8
Samsung Internet Full support 3.0
Deno Full support 1.0
Node.js Full support 0.12.0
@@iterator Chrome Full support 43
Edge Full support 12
Firefox Full support 36
Internet Explorer No support No
Opera Full support 30
Safari Full support 10
WebView Android Full support 43
Chrome Android Full support 43
Firefox for Android Full support 36
Opera Android Full support 30
Safari on iOS Full support 10
Samsung Internet Full support 4.0
Deno Full support 1.0
Node.js Full support 0.12.0
@@species Chrome Full support 51
Edge Full support 13
Firefox Full support 41
Internet Explorer No support No
Opera Full support 38
Safari Full support 10
WebView Android Full support 51
Chrome Android Full support 51
Firefox for Android Full support 41
Opera Android Full support 41
Safari on iOS Full support 10
Samsung Internet Full support 5.0
Deno Full support 1.0
Node.js Full support 6.5.0
@@toStringTag Chrome Full support 44
Edge Full support 79
Firefox Full support 51
Internet Explorer No support No
Opera No support No
Safari Full support 9.1
WebView Android Full support 44
Chrome Android Full support 44
Firefox for Android Full support 51
Opera Android No support No
Safari on iOS Full support 9.3
Samsung Internet Full support 4.0
Deno Full support 1.0
Node.js Full support 6.0.0
Map() constructor Chrome Full support 38
Edge Full support 12
Firefox Full support 13
Internet Explorer Full support 11
Opera Full support 25
Safari Full support 8
WebView Android Full support 38
Chrome Android Full support 38
Firefox for Android Full support 14
Opera Android Full support 25
Safari on iOS Full support 8
Samsung Internet Full support 3.0
Deno Full support 1.0
Node.js Full support 0.12.0
new Map(iterable)
Chrome Full support 38
Edge Full support 12
Firefox Full support 13
Internet Explorer No support No
Opera Full support 25
Safari Full support 9
WebView Android Full support 38
Chrome Android Full support 38
Firefox for Android Full support 14
Opera Android Full support 25
Safari on iOS Full support 9
Samsung Internet Full support 3.0
Deno Full support 1.0
Node.js Full support 0.12.0
Map() without new throws
Chrome Full support 38
Edge Full support 12
Firefox Full support 42
Internet Explorer Full support 11
Opera Full support 25
Safari Full support 9
WebView Android Full support 38
Chrome Android Full support 38
Firefox for Android Full support 42
Opera Android Full support 25
Safari on iOS Full support 9
Samsung Internet Full support 3.0
Deno Full support 1.0
Node.js Full support 0.12.0
new Map(null)
Chrome Full support 38
Edge Full support 12
Firefox Full support 37
Internet Explorer Full support 11
Opera Full support 25
Safari Full support 9
WebView Android Full support 38
Chrome Android Full support 38
Firefox for Android Full support 37
Opera Android Full support 25
Safari on iOS Full support 9
Samsung Internet Full support 3.0
Deno Full support 1.0
Node.js Full support 0.12.0
clear Chrome Full support 38
Edge Full support 12
Firefox Full support 19
Internet Explorer Full support 11
Opera Full support 25
Safari Full support 8
WebView Android Full support 38
Chrome Android Full support 38
Firefox for Android Full support 19
Opera Android Full support 25
Safari on iOS Full support 8
Samsung Internet Full support 3.0
Deno Full support 1.0
Node.js Full support 0.12.0
delete Chrome Full support 38
Edge Full support 12
Firefox Full support 13
Internet Explorer Full support 11
Opera Full support 25
Safari Full support 8
WebView Android Full support 38
Chrome Android Full support 38
Firefox for Android Full support 14
Opera Android Full support 25
Safari on iOS Full support 8
Samsung Internet Full support 3.0
Deno Full support 1.0
Node.js Full support 0.12.0
entries Chrome Full support 38
Edge Full support 12
Firefox Full support 20
Internet Explorer No support No
Opera Full support 25
Safari Full support 8
WebView Android Full support 38
Chrome Android Full support 38
Firefox for Android Full support 20
Opera Android Full support 25
Safari on iOS Full support 8
Samsung Internet Full support 3.0
Deno Full support 1.0
Node.js Full support 0.12.0
forEach Chrome Full support 38
Edge Full support 12
Firefox Full support 25
Internet Explorer Full support 11
Opera Full support 25
Safari Full support 8
WebView Android Full support 38
Chrome Android Full support 38
Firefox for Android Full support 25
Opera Android Full support 25
Safari on iOS Full support 8
Samsung Internet Full support 3.0
Deno Full support 1.0
Node.js Full support 0.12.0
get Chrome Full support 38
Edge Full support 12
Firefox Full support 13
Internet Explorer Full support 11
Opera Full support 25
Safari Full support 8
WebView Android Full support 38
Chrome Android Full support 38
Firefox for Android Full support 14
Opera Android Full support 25
Safari on iOS Full support 8
Samsung Internet Full support 3.0
Deno Full support 1.0
Node.js Full support 0.12.0
has Chrome Full support 38
Edge Full support 12
Firefox Full support 13
Internet Explorer Full support 11
Opera Full support 25
Safari Full support 8
WebView Android Full support 38
Chrome Android Full support 38
Firefox for Android Full support 14
Opera Android Full support 25
Safari on iOS Full support 8
Samsung Internet Full support 3.0
Deno Full support 1.0
Node.js Full support 0.12.0
Key equality for -0 and 0
Chrome Full support 38
Edge Full support 12
Firefox Full support 29
Internet Explorer No support No
Opera Full support 25
Safari Full support 9
WebView Android Full support 38
Chrome Android Full support 38
Firefox for Android Full support 29
Opera Android Full support 25
Safari on iOS Full support 9
Samsung Internet Full support 3.0
Deno Full support 1.0
Node.js Full support 4.0.0
keys Chrome Full support 38
Edge Full support 12
Firefox Full support 20
Internet Explorer No support No
Opera Full support 25
Safari Full support 8
WebView Android Full support 38
Chrome Android Full support 38
Firefox for Android Full support 20
Opera Android Full support 25
Safari on iOS Full support 8
Samsung Internet Full support 3.0
Deno Full support 1.0
Node.js Full support 0.12.0
set Chrome Full support 38
Edge Full support 12
Firefox Full support 13
Internet Explorer Partial support 11
footnote
Opera Full support 25
Safari Full support 8
WebView Android Full support 38
Chrome Android Full support 38
Firefox for Android Full support 14
Opera Android Full support 25
Safari on iOS Full support 8
Samsung Internet Full support 3.0
Deno Full support 1.0
Node.js Full support 0.12.0
size Chrome Full support 38
Edge Full support 12
Firefox Full support 19
footnote
Internet Explorer Full support 11
Opera Full support 25
Safari Full support 8
WebView Android Full support 38
Chrome Android Full support 38
Firefox for Android Full support 19
footnote
Opera Android Full support 25
Safari on iOS Full support 8
Samsung Internet Full support 3.0
Deno Full support 1.0
Node.js Full support 0.12.0
values Chrome Full support 38
Edge Full support 12
Firefox Full support 20
Internet Explorer No support No
Opera Full support 25
Safari Full support 8
WebView Android Full support 38
Chrome Android Full support 38
Firefox for Android Full support 20
Opera Android Full support 25
Safari on iOS Full support 8
Samsung Internet Full support 3.0
Deno Full support 1.0
Node.js Full support 0.12.0

Дивіться також