Пошук уроків, статей та іншого контенту
Освоїте Map для зберігання пар ключ-значення, зокрема з ключами будь-якого типу.
MapMap — це колекція пар ключ-значення. Вона схожа на звичайний об’єкт, але має важливу особливість: ключем може бути значення будь-якого типу:
рядок;
число;
boolean;
об’єкт;
масив;
функція;
Symbol;
інша колекція.
У звичайного об’єкта ключі фактично перетворюються на рядки або Symbol. У Map тип ключа зберігається.
const users = new Map();
users.set(1, "Олена");
users.set(2, "Андрій");
console.log(users.get(1)); // "Олена"
console.log(users.get(2)); // "Андрій"Map зберігає порядок додавання елементів і має спеціальні методи для роботи з колекцією.
MapПорожня колекція створюється за допомогою конструктора Map:
const products = new Map();Також можна одразу передати ітерований об’єкт із парами [ключ, значення]:
const prices = new Map([
["apple", 30],
["banana", 25],
["orange", 40],
]);
console.log(prices.get("apple")); // 30Кожен елемент початкового масиву має бути масивом із двох значень:
const settings = new Map([
["theme", "dark"],
["language", "uk"],
]);Перший елемент пари — ключ, другий — значення.
setМетод set(key, value) додає нову пару або оновлює значення для вже наявного ключа:
const scores = new Map();
scores.set("player1", 100);
scores.set("player2", 150);
console.log(scores);
// Map(2) { 'player1' => 100, 'player2' => 150 }
scores.set("player1", 120);
console.log(scores.get("player1")); // 120Метод set повертає саму колекцію, тому виклики можна об’єднувати в ланцюжок:
const capitals = new Map()
.set("Україна", "Київ")
.set("Франція", "Париж")
.set("Італія", "Рим");
console.log(capitals.get("Франція")); // "Париж"getМетод get(key) повертає значення за ключем:
const userRoles = new Map([
["olena", "admin"],
["andrii", "editor"],
]);
console.log(userRoles.get("olena")); // "admin"
console.log(userRoles.get("unknown")); // undefinedЯкщо ключ не існує, get повертає undefined.
Важливо: undefined може бути і справжнім значенням у колекції. Тому для перевірки наявності ключа краще використовувати has.
hasМетод has(key) перевіряє, чи є ключ у колекції:
const cache = new Map([
["user:1", { name: "Олена" }],
]);
console.log(cache.has("user:1")); // true
console.log(cache.has("user:2")); // falsedeleteМетод delete(key) видаляє пару за ключем і повертає логічне значення:
const tasks = new Map([
[1, "Підготувати звіт"],
[2, "Надіслати лист"],
]);
console.log(tasks.delete(1)); // true
console.log(tasks.delete(99)); // false
console.log(tasks.has(1)); // false
console.log(tasks.size); // 1clearМетод clear() видаляє всі елементи:
const messages = new Map([
["first", "Привіт"],
["second", "Як справи?"],
]);
messages.clear();
console.log(messages.size); // 0Для отримання кількості пар використовується властивість size:
const inventory = new Map([
["keyboard", 10],
["mouse", 25],
]);
console.log(inventory.size); // 2size — це властивість, а не метод. Тому потрібно писати map.size, а не map.size().
Ключами можуть бути не лише рядки:
const values = new Map();
values.set(1, "число");
values.set(true, "логічне значення");
values.set(null, "null");
values.set(undefined, "undefined");
console.log(values.get(1)); // "число"
console.log(values.get(true)); // "логічне значення"Ключі 1 і "1" — різні:
const data = new Map();
data.set(1, "числовий ключ");
data.set("1", "рядковий ключ");
console.log(data.get(1)); // "числовий ключ"
console.log(data.get("1")); // "рядковий ключ"Об’єкт може бути ключем Map:
const user = {
id: 1,
name: "Олена",
};
const permissions = new Map();
permissions.set(user, ["read", "write"]);
console.log(permissions.get(user)); // ["read", "write"]Для об’єктів ключ визначається за посиланням, а не за структурою:
const firstObject = { id: 1 };
const secondObject = { id: 1 };
const map = new Map();
map.set(firstObject, "перше значення");
console.log(map.get(firstObject)); // "перше значення"
console.log(map.get(secondObject)); // undefinedХоча об’єкти мають однакові властивості, це два різні об’єкти в пам’яті.
Те саме стосується масивів і функцій:
const firstArray = [];
const secondArray = [];
const map = new Map();
map.set(firstArray, "дані першого масиву");
console.log(map.get(firstArray)); // "дані першого масиву"
console.log(map.get(secondArray)); // undefinedЩоб отримати значення, потрібно мати саме те посилання, яке використовувалося під час додавання.
undefinedРозглянемо приклад:
const config = new Map([
["timeout", undefined],
]);
console.log(config.get("timeout")); // undefined
console.log(config.get("missing")); // undefinedЗа результатом get неможливо відрізнити наявний ключ зі значенням undefined від відсутнього ключа.
Для цього використовуйте has:
if (config.has("timeout")) {
console.log("Налаштування існує");
}MapMap є ітерованою колекцією. За замовчуванням під час ітерації повертаються пари [ключ, значення].
for...ofconst prices = new Map([
["книга", 300],
["зошит", 50],
["ручка", 25],
]);
for (const [product, price] of prices) {
console.log(`${product}: ${price} грн`);
}Результат:
книга: 300 грн
зошит: 50 грн
ручка: 25 грнПорядок відповідає порядку додавання елементів.
forEachforEach викликає функцію для кожної пари. Його callback отримує значення, ключ і саму колекцію:
const statuses = new Map([
[200, "OK"],
[404, "Not Found"],
[500, "Server Error"],
]);
statuses.forEach((status, code) => {
console.log(`${code}: ${status}`);
});Зверніть увагу на порядок аргументів:
map.forEach((value, key) => {
// ...
});Це відрізняється від деяких методів роботи з масивами, де callback зазвичай отримує елемент, індекс і масив.
keys, values і entrieskeysМетод keys() повертає ітератор ключів:
const users = new Map([
[1, "Олена"],
[2, "Андрій"],
]);
for (const id of users.keys()) {
console.log(id);
}valuesМетод values() повертає ітератор значень:
for (const name of users.values()) {
console.log(name);
}entriesМетод entries() повертає ітератор пар [ключ, значення]:
for (const [id, name] of users.entries()) {
console.log(`${id}: ${name}`);
}Ітерація самого Map еквівалентна ітерації entries():
for (const entry of users) {
console.log(entry);
}Map на масивІтератор можна перетворити на масив за допомогою оператора розпакування:
const map = new Map([
["a", 1],
["b", 2],
]);
const entries = [...map];
const keys = [...map.keys()];
const values = [...map.values()];
console.log(entries); // [["a", 1], ["b", 2]]
console.log(keys); // ["a", "b"]
console.log(values); // [1, 2]Це зручно, якщо потрібно використати методи масиву, наприклад filter або map:
const products = new Map([
["keyboard", 2500],
["mouse", 800],
["monitor", 9000],
]);
const expensiveProducts = [...products].filter(
([, price]) => price > 1000
);
console.log(expensiveProducts);
// [["keyboard", 2500], ["monitor", 9000]]Map часто використовують для кешу — тимчасового зберігання вже обчислених результатів.
const cache = new Map();
function square(number) {
if (cache.has(number)) {
console.log("Результат взято з кешу");
return cache.get(number);
}
console.log("Обчислення результату");
const result = number * number;
cache.set(number, result);
return result;
}
console.log(square(5)); // Обчислення результату, 25
console.log(square(5)); // Результат взято з кешу, 25
console.log(square(7)); // Обчислення результату, 49Алгоритм роботи функції:
Перевірити, чи є результат у кеші.
Якщо є — повернути збережене значення.
Якщо немає — виконати обчислення.
Зберегти результат у Map.
Повернути результат.
Map зручно використовувати для групування даних за ключем.
const orders = [
{ id: 1, customer: "Олена", status: "new" },
{ id: 2, customer: "Андрій", status: "paid" },
{ id: 3, customer: "Марія", status: "new" },
{ id: 4, customer: "Ігор", status: "paid" },
];
const ordersByStatus = new Map();
for (const order of orders) {
if (!ordersByStatus.has(order.status)) {
ordersByStatus.set(order.status, []);
}
ordersByStatus.get(order.status).push(order);
}
for (const [status, groupedOrders] of ordersByStatus) {
console.log(status, groupedOrders);
}У результаті кожен статус пов’язаний із масивом відповідних замовлень.
Такий підхід особливо корисний, коли ключі можуть бути складними значеннями або коли потрібні методи Map.
Map і звичайний об’єктДля простих структур із фіксованими полями звичайний об’єкт часто є зрозумілішим:
const user = {
name: "Олена",
age: 28,
};Map краще підходить, коли:
ключі не обмежуються рядками;
потрібно часто додавати або видаляти пари;
потрібно легко отримувати кількість елементів через size;
важливий порядок вставки;
потрібна семантика саме колекції ключ-значення;
ключами є об’єкти або інші значення за посиланням.
Порівняння основних операцій:
const object = {};
object.name = "Олена";
const map = new Map();
map.set("name", "Олена");
console.log(object.name);
console.log(map.get("name"));У Map для роботи з даними використовуються спеціальні методи:
додавання — set;
отримання — get;
перевірка — has;
видалення — delete;
очищення — clear;
кількість — size.
Не слід без потреби замінювати всі об’єкти на Map. Вибір залежить від моделі даних і способу використання.
MapДля створення поверхневої копії можна передати наявну колекцію в конструктор:
const original = new Map([
["theme", "dark"],
["language", "uk"],
]);
const copy = new Map(original);
copy.set("theme", "light");
console.log(original.get("theme")); // "dark"
console.log(copy.get("theme")); // "light"Копіюється сама структура Map, але не вкладені об’єкти:
const original = new Map([
["user", { name: "Олена" }],
]);
const copy = new Map(original);
copy.get("user").name = "Андрій";
console.log(original.get("user").name); // "Андрій"Обидві колекції посилаються на той самий вкладений об’єкт.
Для ключів Map використовується алгоритм, близький до Object.is.
Зокрема:
1 і "1" — різні ключі;
true і 1 — різні ключі;
два різні об’єкти — різні ключі;
NaN вважається тим самим ключем, що й інший NaN;
0 і -0 вважаються однаковим ключем.
const map = new Map();
map.set(NaN, "значення для NaN");
console.log(map.get(NaN)); // "значення для NaN"size як методуНеправильно:
const map = new Map();
console.log(map.size());Правильно:
console.log(map.size);getНенадійно:
if (map.get("count")) {
// Значення 0 або false не пройдуть цю перевірку
}Якщо значенням може бути 0, false, "" або undefined, використовуйте has:
if (map.has("count")) {
console.log("Ключ існує");
}const map = new Map();
map.set({ id: 1 }, "користувач");
console.log(map.get({ id: 1 })); // undefinedОб’єкт у get створюється заново, тому має інше посилання.
Правильно — зберегти посилання:
const user = { id: 1 };
const map = new Map();
map.set(user, "користувач");
console.log(map.get(user)); // "користувач"Map серіалізується як звичайний об’єктПід час використання JSON.stringify дані Map не перетворюються на очікуваний список пар:
const map = new Map([
["name", "Олена"],
]);
console.log(JSON.stringify(map)); // "{}"Якщо потрібно серіалізувати Map, спочатку перетворіть його на масив:
const map = new Map([
["name", "Олена"],
["age", 28],
]);
const json = JSON.stringify([...map]);
console.log(json); // [["name","Олена"],["age",28]]Відновити колекцію можна так:
const restoredMap = new Map(JSON.parse(json));
console.log(restoredMap.get("name")); // "Олена"Такий спосіб підходить, якщо ключі та значення можна коректно представити у форматі JSON.
Map зберігає пари ключ-значення.
Ключем може бути значення будь-якого типу.
Для роботи з колекцією використовуються set, get, has, delete і clear.
Кількість елементів доступна через властивість size.
Map зберігає порядок додавання елементів.
Об’єкти як ключі порівнюються за посиланням.
Для перебору використовують for...of, forEach, keys, values і entries.
Map зручний для кешування, групування даних і колекцій із довільними ключами.
Для простих структур із фіксованими властивостями часто достатньо звичайного об’єкта.
Під час роботи з undefined, false, 0 або порожніми рядками для перевірки наявності ключа потрібно використовувати has.