Буває таке, що сидиш хвилину, дві, пʼять, десять, дивлячись у код; йдеш і повертаєшся, і… все одно не знаєш, яку структуру використати. Точно знаю, що у моїх студентів так буває, і колись так було і в мене.
Іноді трапляється так: що більше ти знаєш, то важче тобі підібрати не просто правильне рішення, а ідеально оптимізований варіант цього рішення, щоб усе не просто працювало, а було ідеально. Зізнавайтеся, було?
Розробники загалом, не тільки Front-End, часто витрачають багато часу на вирішення того, яку структуру даних слід використовувати. Це відбувається через те, що вибір правильної структури даних з великою ймовірністю спростить подальше управління цими даними, заощаджуючи час і покращить розуміння коду загалом.
ЩО ТАКЕ СТРУКТУРА ДАНИХ?
Ви вже точно стикалися зі структурами даних, навіть якщо не розумієтете цього, тож розберімося.
Структура даних — це те, як саме організовані дані. Наприклад масив — упорядкована структура, при видаленні з якої повинна відбутися переіндексація елементів, якщо дані видалені не з кінця.
І тут відразу говоримо про звичайний Object, це в нас пари ключ і значення, де ключ — завжди string, а значення може бути в будь-якому типі. При видаленні даних з обʼєкта в нас ніяких переіндексацій не буде, тому що обʼєкт не має індексів і взагалі його елементам не важливо в якому порядку вони стоять.
Звісно ми це і так все знаємо, бо ми до того звикли, але все ж увагу варто приділити внутрішньому устрою і те, як воно працює всередині. Оскільки хоч ми й використовуємо зазвичай Array та звичайний Object у більшості випадків, це не означає, що немає більш відповідних інструментів, які могли б бути або зручнішими, або ефективнішими, а може і те й інше разом.
Я не буду вдаватись у деталі роботи тих структур, про які ми говорили вище, адже я впевнений, що ви й так добре знаєте як вони працюють. Натомість ми розберемо інші стандартні структури даних у JavaScript, які вартувало б знати й розуміти, а саме: Map, Set, WeakMap, WeakSet.
MAP
Map — колекція «ключ: значенн», як і Object, але (куди ж без але), основна відмінність полягає у тому, що Map дозволяє використовувати ключі будь-якого типу. І тут вже можна потирати руки й радіти. Тобто ключем в структурі Map може виступати як примітивний тип даних, так і будь-який різновид Object’у і це додає багато гнучкості при його використанні.
У Map порядок додавання пар «ключ-значення» зберігається. Якщо ви додали елементи в певному порядку, вони будуть перебиратися в тому ж порядку. Map оптимізований для частих операцій додавання, видалення та пошуку елементів. Ну і звісно Map знає скільки елементів у ньому зберігається, за це відповідає властивість size.
Синтаксис:
Map створюється за допомогою конструктора new Map() Синтаксис створення Map має такий вигляд:
const map = new Map();
Далі в змінній буде лежати ось такий об'єкт:
Map(0) {
size: 0 // кількість значень у Map
}
Ми отримали кастомний об’єкт типу map. На цьому моменті у вас могло зʼявитися запитання: «якщо це обʼєкт, то може в ньому є методи?». І відповідь на це — так, і цих методів достатньо багато.
МЕТОДИ MAP
1. set(key, value)
Цей метод додає нову пару «ключ: значення» в Map. Якщо ключ уже існує, його значення буде замінене новим.
const map = new Map();
map.set('name', 'Odesa');
map.set('population', 1000000);
console.log(map);
// Map(2) { 'name' => 'Odesa', 'population' => 1000000 }
Кожен виклик map.set повертає об’єкт map. Це означає, що таким чином ми можемо об’єднати виклики в ланцюжок:
map.set('1', 'str1')
.set(1, 'num1')
.set(true, 'bool1');
2. delete(key)
Видаляє пару «ключ: значення» за вказаним ключем. Якщо ключ вже існує, то метод поверне true, якщо ні — false.
map.delete('name');
console.log(map);
// Map(1) { 'population' => 1000000 }
3. has(key)
Перевіряє наявність елемента з певним ключем. Повертає true або false.
console.log(map.has('population')); // true
console.log(map.has('name')); // false
4. get(key)
Повертає значення, яке відповідає заданому ключу. Якщо ключ відсутній, поверне undefined.
console.log(map.get('population')); // 1000000
console.log(map.get('name')); // undefined
5. clear()
Видаляє всі пари ключ/значення з Map.
map.clear();
console.log(map);
// Map(0) { size: 0 }
ПЕРЕБИРАННЯ MAP
Map підтримує кілька способів ітерації:
1. forEach(callback, thisArg)
Викликає callback для кожної пари ключ/значення.
const cities = new Map([
['Odesa', 'Ukraine'],
['Berlin', 'Germany'],
['Paris', 'France']
]);
cities.forEach((value, key, map) => {
console.log(`${key} is in ${value}`);
});
// Odesa is in Ukraine
// Berlin is in Germany
// Paris is in France
2. for...of
За допомогою for...of можна ітерувати Map через entries, keys, або values:
// Перебір ключів
for (let key of cities.keys()) {
console.log(key);
}
// Odesa
// Berlin
// Paris
// Перебір значень
for (let value of cities.values()) {
console.log(value);
}
// Ukraine
// Germany
// France
// Перебір пар ключ/значення
for (let [key, value] of cities.entries()) {
console.log(`${key} => ${value}`);
}
// Odesa => Ukraine
// Berlin => Germany
// Paris => France
ДЛЯ ЯКИХ ЗАДАЧ ВИКОРИСТОВУВАТИ MAP?
Наприклад, кешування запитів або результатів обчислень. Ви можете зберігати результати операцій, які часто виконуються, щоб уникнути повторних обчислень.
const cache = new Map();
function calculateSquare(num) {
if (cache.has(num)) {
console.log('Fetching from cache');
return cache.get(num);
}
console.log('Calculating result');
const result = num * num;
cache.set(num, result);
return result;
}
console.log(calculateSquare(4)); // Calculating result → 16
console.log(calculateSquare(4)); // Fetching from cache → 16
Такий спосіб використання дозволить нам підвищити продуктивність і додасть можливість пропустити виклик важкого коду в разі, якщо ці обчислення вже були зроблені раніше.
Ще один приклад: збереження метаданих або додаткових властивостей об'єктів.
const metaData = new Map();
const button = document.createElement('button');
metaData.set(button, { clicked: 0 });
button.addEventListener('click', () => {
const data = metaData.get(button);
data.clicked += 1;
console.log(`Clicked ${data.clicked} times`);
});
Таким чином ми не розширюємо об'єкт і не впливаємо на його внутрішню структуру, але водночас маємо можливість використовувати його для зберігання його додаткових даних.
SET
Set — це колекція унікальних значень. Кожне значення може з'являтися в Set лише один раз. Якщо вам потрібно зберігати унікальні елементи, Set стане ідеальним рішенням.
На відміну від масивів, Set не має індексів і не підтримує доступ до елементів за індексом. Він оптимізований для додавання, видалення та перевірки наявності елементів.
Основні особливості Set:
- Унікальність елементів: немає дублікатів.
- Порядок додавання: елементи перебираються у порядку їх додавання.
- Розмір Set: властивість size показує кількість елементів у колекції.
Синтаксис:
Set створюється за допомогою конструктора new Set():
const set = new Set();
Set можна відразу ініціалізувати значеннями:
const set = new Set([1, 2, 3, 4, 4]);
console.log(set);
// Set(4) {1, 2, 3, 4}
Зверніть увагу: дублікат значення 4 було автоматично видалено.
МЕТОДИ SET
Не дивлячись на те, що вони з Map не схожі, у Set також є свої методи.
1. add(value)
Додає нове значення до Set. Якщо значення вже є у колекції, воно не буде додано повторно.
const set = new Set();
set.add(1);
set.add(2);
set.add(2); // Не додасться, бо вже є
console.log(set);
// Set(2) {1, 2}
Метод add повертає сам об'єкт Set, що дозволяє використовувати ланцюжки викликів:
set.add(3).add(4).add(5);
console.log(set);
// Set(5) {1, 2, 3, 4, 5}
2. delete(value)
Видаляє вказане значення з Set. Повертає true, якщо елемент було видалено, і false, якщо такого елемента не існувало.
set.delete(3);
console.log(set);
// Set(4) {1, 2, 4, 5}
3. has(value)
Перевіряє, чи є значення у Set. Повертає true або false.
console.log(set.has(2)); // true
console.log(set.has(10)); // false
4. clear()
Видаляє всі елементи з Set.
set.clear();
console.log(set);
// Set(0) {}
5. size
Властивість, яка показує кількість елементів у Set.
console.log(set.size); // 0
ПЕРЕБИРАННЯ SET
1. forEach(callback, thisArg)
Викликає callback для кожного елемента.
const numbers = new Set([1, 2, 3]);
numbers.forEach((value) => {
console.log(value);
});
// 1
// 2
// 3
2. for...of
Set можна перебирати за допомогою циклу for...of:
for (let value of numbers) {
console.log(value);
}
// 1
// 2
// 3
3. keys(), values(), entries()
- keys() — повертає ітератор за значеннями (ідентичний values() у Set).
- values() — повертає ітератор за значеннями.
entries() — повертає ітератор парами [value, value] (щоб підтримати сумісність з Map).
for (let value of numbers.values()) {
console.log(value);
}
// 1
// 2
// 3
for (let [key, value] of numbers.entries()) {
console.log(key, value);
}
// 1 1
// 2 2
// 3 3
ДЕ ВИКОРИСТОВУВАТИ SET?
Унікальні значення у масиві:
const array = [1, 2, 3, 3, 4, 5, 5];
const uniqueValues = new Set(array);
console.log([...uniqueValues]);
// [1, 2, 3, 4, 5]
Видалення дублікатів з масиву:
function removeDuplicates(arr) {
return [...new Set(arr)];
}
console.log(removeDuplicates([1, 2, 2, 3, 4, 4]));
// [1, 2, 3, 4]
Перевірка унікальних елементів:
function hasDuplicate(arr) {
return new Set(arr).size !== arr.length;
}
console.log(hasDuplicate([1, 2, 3, 4])); // false
console.log(hasDuplicate([1, 2, 3, 3])); // true
Кешування виконаних операцій:
const processed = new Set();
function processItem(item) {
if (processed.has(item)) {
console.log('Item already processed:', item);
return;
}
console.log('Processing:', item);
processed.add(item);
}
processItem('task1'); // Processing: task1
processItem('task1'); // Item already processed: task1
ВИСНОВОК
Правильний вибір структури даних — це не просто питання технічної компетенції, а й уміння бачити трохи глибше, ніж поверхневий код. Map і Set — дві потужні структури, які часто можуть стати більш ефективними та зручними альтернативами звичним Object і Array, якщо їх використовувати у відповідних сценаріях.
Map чудово підходить для ситуацій, коли важливо зберегти зв'язок між ключами та значеннями, і коли ключі можуть мати різні типи. Він дозволяє легко додавати, видаляти й отримувати дані, забезпечуючи при цьому збереження порядку вставки.
Set, зі свого боку, є ідеальним рішенням для збереження унікальних значень. Його можна використовувати для видалення дублікатів, перевірки унікальності даних, або як інструмент кешування вже оброблених елементів.
Тож, перш ніж обрати Object або Array за звичкою, зробіть крок назад, проаналізуйте задачу і подумайте: можливо, Map чи Set — це саме те, що вам потрібно?