Зовсім недавно ми з вами детально розібрали Map і Set. Там ми зʼясували, що Map — це структура даних, яка чудово підходить для зберігання пар ключ-значення, де ключ може бути будь-якого типу, а Set — це колекція унікальних значень. І Map, і Set часто виступають більш ефективними, ніж звичні нам Object і Array.
Але, звісно ж, на цьому JavaScript не обмежується, адже крім Map і Set існують їх дуже корисні «слабкі» аналоги WeakMap і WeakSet.
Тож якщо ви досі не читали попередню статтю, то я щиро рекомендую зробити це до ознайомлення з цією статею, бо конкретно в цій я буду дуже часто робити відсилання на попередню статтю:
ВСТУП
У роботі над великими проєктами може бути (частіше за все є) купа різних за специфічністю завдань, які стосуються керуванням памʼяттю, оптимізації роботи з обʼєктами й загалом з даними, уникнення витоків памʼяті та зміни життєвого циклу тощо.
Маю пояснити, що таке ті витоки даних, щоб далі було простіше розуміти.
Виток даних — це проблема в програмуванні, коли дані або об’єкти зберігаються в пам’яті, навіть після того, як вони більше не використовуються або не потрібні.
у результаті цього програма не може звільнити пам’ять, яка була зайнята старими даними, що призводить до збільшення споживання пам’яті й може викликати її переповнення. Витоки даних можуть спричинити серйозні проблеми з продуктивністю програми, тому важливо вчасно видаляти непотрібні об’єкти й використовувати механізми управління пам’яттю.
Буває таке, що деякі дані вам потрібно залишати, так скажемо «під капотом», не показуючи їх на загал; також деяким з ваших обʼєктів потрібно просто «зникнути» рівно в той момент, коли вони стануть непотрібними для подальшої логіки, тобто «живуть» ці обʼєкти доволі короткий час.
От саме в ці моменти для роботи найкраще підходять WeakMap і WeakSet. Тож почнімо розбиратися що тут і до чого.
WEAKMAP
Доволі часто WeakMap називають сховищем для слабких ключів. І слово «слабкий» у цій назві зовсім не означає нічого поганого, бо це навпаки полегшує нам деяку роботу. Трошки про слабкі звʼязки.
Слабкі звʼязки (weak references) — це спеціальний тип посилань у програмуванні, який дозволяє обʼєктам бути доступними для використання, але не утримує їх у памʼяті, коли на них більше не існує сильних посилань. Якщо обʼєкт є доступним лише через слабке посилання, збирач сміття автоматично видаляє його з пам’яті, коли він більше не потрібен.
Це дозволяє уникнути витоків памʼяті, оскільки обʼєкти, що більше не використовуються, будуть видалені без втручання розробника. Слабкі звʼязки часто застосовуються в структурах даних типу WeakMap і WeakSet, про які ми сьогодні говоримо.
Як можна зрозуміти з назви, WeakMap схожий на Map тим, що також зберігає «ключ: значення», але, само собою, тут є ряд відмінностей (інакше навіщо придумувати дві однакові штуки, правда?):
- Ключі у WeakMap можуть бути лише типу Object:
Тобто якщо ви спробуєте використати будь-який примітив як ключ, то це спровокує виникнення помилки. А повʼязано це з тим, що WeakMap дуже пасує до сценаріїв, де потрібно швиденько видалити обʼєкти, які більше не будуть використовуватися (далі стане зрозуміліше).
- Слабка привʼязка до обʼєктів:
Наприклад, у Map обʼєкти, які є ключами, залишаються в памʼяті доти, доки вони є ключами. А у WeakMap такого немає: як тільки ключ більше не потрібен — це значить, що й обʼєкт також не потрібен. Цей обʼєкт перестає бути доступним у коді, бо автоматично видаляється з WeakMap.
Звісно, у WeakMap є свої особливості, такі як недоступність size й ітераторів. Це означає, що, на відміну від Map, у WeakMap немає таких методів, за допомогою яких можна отримати всі ключі, всі значення чи всі пари. Зроблено це для того, щоб захиститися від витоку даних.
WeakMap має всього чотири методи:
- set (key, value) — дозволяє додати пару «ключ: значення». Якщо ключ уже існує, його значення буде оновлено;
- get (key) — повертає значення за заданим ключем, або undefined, якщо ключ не знайдено;
- delete (key) — видаляє пару за ключем;
- has (key) — перевіряє, чи є такий ключ у колекції.
ЯК МОЖНА ВИКОРИСТОВУВАТИ WEAKMAP?
Розглянемо ситуацію, де ми хочемо зберегти приватні дані обʼєкта. У цьому прикладі приватні дані користувача зберігаються в WeakMap і ми не можемо отримати доступ до них безпосередньо.
const privateData = new WeakMap();
class User {
constructor(name) {
privateData.set(this, { name });
}
getName() {
return privateData.get(this).name;
}
}
const user = new User("Alice");
console.log(user.getName()); // Alice
// Якщо об'єкт user більше не використовується, його дані в WeakMap теж зникнуть.
Кешування результатів функції. Маємо функцію для обчислення деяких складних даних, наприклад, результатів певних запитів до бази даних чи дорогих обчислень.
const cache = new WeakMap();
function expensiveComputation(obj) {
// Якщо результат обчислення вже зберігається в кеші, просто повертаємо його.
if (cache.has(obj)) {
console.log('Отримано результат з кешу');
return cache.get(obj);
}
// Інакше, виконуємо складне обчислення.
console.log('Обчислюємо нові дані...');
const result = obj.value * 10; // Уявімо, що це складне обчислення
// Зберігаємо результат обчислення в кеші.
cache.set(obj, result);
return result;
}
const obj1 = { value: 5 };
const obj2 = { value: 7 };
console.log(expensiveComputation(obj1)); // Обчислюємо нові дані...
console.log(expensiveComputation(obj1)); // Отримано результат з кешу
console.log(expensiveComputation(obj2)); // Обчислюємо нові дані...
WEAKSET
WeakSet — це колекція обʼєктів, у якій кожен обʼєкт може зʼявитися лише один раз. Це про унікальні ключі з обмеженим доступом. Також, як і у випадку з WeakMap, ця структура використовує слабкі посилання, тому об’єкти, які більше не використовуються, будуть автоматично видалені.
Відмінності від Set:
- WeakSet може зберігати тільки обʼєкти, а примітиви не підтримуються ним зовсім;
- так само слабка привʼязка — якщо обʼєкт не використовується, то він видаляється;
- немає способу отримати список усіх елементів (values(), keys(), entries() відсутні);
- так само як і у WeakMap, немає ітераторів, і це робить WeakSet більш захищеним.
Методи WeakSet:
- add (value) — додати обʼєкт до колекції;
- has (value) — перевіряє, чи є обʼєкт у колекції;
- delete (value) — видаляє обʼєкт із колекції.
ЯК ВИКОРИСТОВУВАТИ WEAKSET?
Розглянемо ситуацію, де WeakSet використовується для відстеження оброблених обʼєктів. У цьому прикладі WeakSet автоматично видалить обʼєкти, коли вони перестануть використовуватися:
const processed = new WeakSet();
function process(obj) {
if (processed.has(obj)) {
console.log("Об'єкт вже оброблений");
return;
}
console.log("Обробка об'єкта...");
processed.add(obj);
}
const task1 = {};
const task2 = {};
process(task1); // Обробка об'єкта...
process(task1); // Об'єкт вже оброблений
process(task2); // Обробка об'єкта...
Відстеження активних користувачів. Нам потрібно відстежувати активних користувачів на сайті. При цьому важливо, щоб коли користувач вийде з сайту і його об’єкт більше не буде використовуватися, він автоматично видалявся з колекції відстежених користувачів, щоб не виникало витоків пам’яті.
const activeUsers = new WeakSet();
class User {
constructor(name) {
this.name = name;
this.loggedIn = false;
}
login() {
this.loggedIn = true;
activeUsers.add(this);
}
logout() {
this.loggedIn = false;
activeUsers.delete(this);
}
}
const user1 = new User('Alice');
const user2 = new User('Bob');
user1.login();
user2.login();
console.log(activeUsers.has(user1)); // true
console.log(activeUsers.has(user2)); // true
// Користувач 1 виходить з сайту
user1.logout();
console.log(activeUsers.has(user1)); // false
console.log(activeUsers.has(user2)); // true
// user2 більше не потрібен - він буде автоматично видалений з activeUsers, коли перестане використовуватися
ПОРІВНЯННЯ
Тепер нарешті, завершуючи дві статті про ці конкретні структурам даних, хочу запропонувати вашій увазі порівняльну таблицю:
Характеристика | Map | Set | WeakMap | WeakSet |
Тип даних для ключів/значень | Ключі: будь-які типи. Значення: будь-які типи. | Значення: будь-які типи. | Ключі: тільки об’єкти. Значення: будь-які типи. | Значення: тільки об’єкти. |
Унікальність ключів/значень | Унікальні ключі. Значення можуть повторюватися. | Унікальні значення. | Унікальні ключі (об’єкти). Значення можуть повторюватись. | Унікальні об’єкти. |
Доступ до даних | Можна отримати ключі, значення або і те, і те через ітерацію або методи (keys(), values(), entries()). | Можна отримати всі значення через ітерацію або методи (values(), entries()). | Доступ до елементів можливий тільки через ключ (методи: get(), set(), has(), delete()). | Доступ до елементів тільки через об’єкти (методи: add(), has(), delete()). |
Ітерація | Так (forEach(), for...of, keys(), values(), entries()). | Так (forEach(), for...of, values(), entries()). | Ні. Немає можливості ітерувати ключі чи значення. | Ні. Ітерація недоступна. |
Збирання сміття | Ключі та значення не видаляються автоматично. | Значення не видаляються автоматично. | Ключі видаляються автоматично, якщо на об’єкт немає інших посилань. | Об’єкти видаляються автоматично, якщо на них немає інших посилань. |
Методи додавання | set(key, value) | add(value) | set(key, value) | add(value) |
Методи перевірки | has(key) | has(value) | has(key) | has(value) |
Методи видалення | delete(key) | delete(value) | delete(key) | delete(value) |
Отримання значення | get(key) | Немає. | get(key) | Немає. |
Розмір колекції | size | size | Немає. Розмір WeakMap не відображається. | Немає. Розмір WeakSet не відображається. |
Придатність для кешування | Можна, але потрібно вручну видаляти невикористовувані дані. | Непридатний через слабке управління ключами. | Ідеально підходить, оскільки ключі автоматично видаляються, якщо об’єкт більше не використовується. | Підходить для зберігання унікальних об’єктів без ризику витоків пам’яті. |
Основні сценарії використання | Зберігання пар ключ-значення з будь-якими типами даних, ітерація даних. | Зберігання унікальних значень будь-яких типів. | Зберігання метаданих об’єктів, кешування, прив’язка приватних даних до об’єктів. | Відстеження унікальних об’єктів, контроль доступу до об’єктів. |
ВИСНОВОК
Тепер, коли ми з вами познайомилися ближче з цими чотирма структурами даних, то ми можемо підсумувати.
WeakMap і WeakSet — це чудові інструменти, створені не для заміни Map і Set, а для розв'язання специфічних задач, пов’язаних із керуванням пам’яттю, збереженням приватності даних й оптимізацією роботи з об’єктами, адже їх особливості роблять їх незамінними у великих проєктах.
Також дуже важливо завжди тримати в голові їх обмеження, щоб використовувати все правильно й уникнути всяких «ой», коли доведеться виправляти помилки:)
Це точно було не відмовляння використовувати ці інструменти, бо без них іноді дуже складно написати класний і якісний код, який ми всі так сильно любимо.
На цьому моменті прощаємося і до зустрічі в цей самий час і в цьому самому місці!