Що робить WeakRef
WeakRef (weak reference) це об'єкт, який містить слабке посилання на інший об'єкт, тобто посилання, що не заважає збирачу сміття видалити цей об'єкт. Простіше кажучи, WeakRef дозволяє «пам'ятати» об'єкт, поки він живий, але не утримувати його в пам'яті штучно.
Теорія
TL;DR
new WeakRef(obj)створює слабке посилання наobj.weakRef.deref()повертає об'єкт абоundefined, якщо GC його вже зібрав.- Слабке посилання не заважає збиранню сміття, на відміну від звичайного.
- Момент запуску GC непередбачуваний, тому час життя об'єкта гарантувати не можна.
- Корисний у вузьких сценаріях: кеш без витоків, мемоізація, спостерігачі, важкі ресурси.
FinalizationRegistryдає callback, коли об'єкт зібрано, але без гарантій за часом.- Доданий у ES2021; у звичайному коді потреби в ньому зазвичай немає.
Швидкий приклад
let user = { name: 'Tim' };
const weakRef = new WeakRef(user);
user = null; // there are no strong references any more
// ...later, maybe...
console.log(weakRef.deref()?.name); // 'Tim' or undefinedТепер об'єкт може бути видалений GC будь-якої миті, і тоді weakRef.deref() поверне undefined.
Сильні та слабкі посилання
Звичайне (сильне) посилання утримує об'єкт у пам'яті:
let user = { name: 'Tim' };
let ref = user; // an ordinary, strong reference
user = null; // but ref still holds the object
console.log(ref.name); // 'Tim'Об'єкт не буде видалений збирачем сміття, поки існує хоча б одне сильне посилання (ref).
Слабке посилання поводиться інакше: воно не рахується при вирішенні того, чи об'єкт ще потрібен.
Як працює WeakRef
new WeakRef(obj)створює слабке посилання.weakRef.deref()намагається повернути оригінальний об'єкт, якщо він ще живий.- Якщо об'єкт уже зібрано GC,
deref()повернеundefined.
Реальна поведінка виглядає так:
let ref;
(() => {
let bigObject = { data: 'Some heavy data...' };
ref = new WeakRef(bigObject);
})();
// bigObject is no longer reachable directly
setTimeout(() => {
console.log(ref.deref()); // either the object or undefined, up to the GC
}, 1000);Збирач сміття запускається у невизначений момент. Передбачити, коли саме deref() почне повертати undefined, неможливо.
Навіщо потрібен WeakRef
Зазвичай у JavaScript не треба дбати про ручне керування пам'яттю, але є вузькі випадки, де WeakRef корисний:
| Сценарій | Приклад |
|---|---|
| Кешування без витоків | Хочемо кешувати об'єкти, але не тримати їх у пам'яті «вічно» |
| Мемоізація | Зберігати результат обчислення для об'єкта, поки той живий |
| Реактивні системи та спостерігачі | Стежити за об'єктом, але не заважати його збиранню |
| Ігрові рушії та графіка | Кешувати важкі ресурси (текстури, дані), але дозволяти GC їх очищати |
| Інтеграція з FinalizationRegistry | Реагувати, коли об'єкт зібрано GC |
Звичайний Map утримує ключі сильно:
const cache = new Map();
function getUserData(user) {
if (!cache.has(user)) {
cache.set(user, heavyCompute(user));
}
return cache.get(user);
}Проблема в тому, що навіть коли user більше не потрібен, Map не дасть GC його видалити, доки кеш не очистять вручну. Рішення, зберігати у кеші слабке посилання:
const cache = new Map();
function getUserData(user) {
const ref = cache.get(user);
let data = ref?.deref();
if (!data) {
data = heavyCompute(user);
cache.set(user, new WeakRef(data));
}
return data;
}Тепер, якщо data більше ніде не використовується, GC її звільнить, і ref.deref() просто поверне undefined.
Зв'язка з FinalizationRegistry
Щоб дізнатися, коли об'єкт було зібрано GC, використовують FinalizationRegistry:
const registry = new FinalizationRegistry(id => {
console.log(`Object ${id} was removed from memory`);
});
let user = { name: 'Tim' };
registry.register(user, 'user#1');
let ref = new WeakRef(user);
user = null;Коли GC збере user, викличеться callback:
Object user#1 was removed from memoryВажливо: виклик не гарантований за часом і може взагалі не статися до завершення процесу.
Коли не варто використовувати WeakRef
Не використовуйте його:
- для керування звичайними даними;
- щоб «пришвидшити» GC, це марно;
- для зберігання стану в React, Vue чи Next.js;
- у критичних логічних ланцюжках, де результат не може раптово зникнути.
Що треба пам'ятати:
| Особливість | Опис |
|---|---|
WeakRef не запобігає збиранню | Об'єкт може зникнути будь-якої миті |
deref() може повернути undefined | Якщо GC уже видалив об'єкт |
| Не можна покладатися на час життя | GC непередбачуваний |
| Не використовувати без потреби | Може призвести до «зомбі»-помилок, коли об'єкт зник раптово |
| Корисний лише у спеціальних сценаріях | Кеш, мемоізація, інтеграція з GC |
Підсумок:
| Питання | Відповідь |
|---|---|
| Що робить | Створює слабке посилання на об'єкт |
| Як працює | Не заважає GC видалити об'єкт |
| Метод | .deref() повертає об'єкт або undefined |
| Корисний для | Кешів, мемоізації, відстеження пам'яті |
| Ризики | Об'єкт може зникнути будь-якої миті |
| З'явився в | ES2021 |
Типові помилки
- Очікують, що
deref()завжди поверне об'єкт. Він може повернутиundefined; завжди перевіряйте результат. - Кешують результат
deref()у довгоживучій змінній. Це створює сильне посилання і зводить нанівець сенсWeakRef. - Плутають
WeakRefзWeakMap.WeakMapробить слабкими саме ключі і не дає їх перебирати,WeakRefце одне посилання на одне значення. - Розраховують на
FinalizationRegistryяк на деструктор. Callback може не спрацювати взагалі, тому звільняти файли чи сокети через нього не можна. - Використовують
WeakRef, щоб «допомогти» збирачу сміття. GC працює за власним розкладом, слабке посилання його не пришвидшує. - Тестують поведінку без урахування непередбачуваності GC. Тест, який чекає
undefinedпісляsetTimeout, буде нестабільним.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.