WeakRef и FinalizationRegistry
WeakRef— слабая ссылка на объект, не препятствующая его сборке GC;FinalizationRegistry— механизм регистрации callback, вызываемого когда объект был собран сборщиком мусора (ES2021).
Зачем нужно
WeakRef решает задачи, где нужен доступ к объекту, но без удержания в памяти — например, кэш, который должен «протухать» при нехватке памяти. FinalizationRegistry позволяет выполнить cleanup-операции после удаления объекта (например, освободить внешний ресурс). Оба API низкоуровневые и нужны редко.
Где используется
- Реализация кэша, который не мешает GC
- Освобождение внешних ресурсов (WebGL текстуры, файловые дескрипторы)
- Мониторинг утечек памяти
- Ослабленные ссылки на DOM-элементы в сложных структурах данных
Основной контент
WeakRef
// WeakRef хранит слабую ссылку
let target = { data: 'Важные данные', size: 'large' };
const ref = new WeakRef(target);
// Получить объект: deref — вернёт объект или undefined
let obj = ref.deref;
console.log(obj?.data); // 'Важные данные'
// Пока target в памяти — deref работает
target = null; // убираем сильную ссылку
// После GC (момент непредсказуем):
// ref.deref → undefined
// ВАЖНО: всегда проверяйте результат deref
function processIfAlive(weakRef) {
const obj = weakRef.deref;
if (obj === undefined) {
console.log('Объект был собран GC');
return;
}
console.log('Обрабатываем:', obj.data);
}
Кэш с WeakRef
// Кэш, который «протухает» при нехватке памяти
class WeakCache {
#cache = new Map();
get(key) {
const ref = this.#cache.get(key);
if (!ref) return undefined;
const value = ref.deref;
if (value === undefined) {
// GC собрал объект — чистим запись
this.#cache.delete(key);
return undefined;
}
return value;
}
set(key, value) {
this.#cache.set(key, new WeakRef(value));
}
has(key) {
return this.get(key) !== undefined;
}
}
const cache = new WeakCache();
let bigObject = { data: new Array(100000).fill('x') };
cache.set('big', bigObject);
console.log(cache.has('big')); // true
bigObject = null;
// После GC: cache.has('big') → false
FinalizationRegistry
// Регистрируем callback для cleanup после сборки объекта
const registry = new FinalizationRegistry((heldValue) => {
// heldValue — значение, переданное при регистрации (не объект!)
console.log(`Объект "${heldValue}" был удалён GC`);
// Cleanup: освободить внешние ресурсы
});
function createManagedResource(name) {
const resource = { name, data: new Array(10000).fill(0) };
// Регистрируем: когда resource будет собран — вызовем callback
registry.register(resource, name); // resource — объект, name — held value
return resource;
}
let res = createManagedResource('texture-1');
res = null;
// Когда GC соберёт res: 'Объект "texture-1" был удалён GC'
WeakRef + FinalizationRegistry вместе
class ResourceManager {
#refs = new Map();
#registry = new FinalizationRegistry((id) => {
console.log(`Ресурс ${id} освобождён`);
this.#refs.delete(id);
});
add(id, resource) {
this.#refs.set(id, new WeakRef(resource));
this.#registry.register(resource, id);
}
get(id) {
return this.#refs.get(id)?.deref;
}
}
Частые ошибки
- Полагаться на немедленную сборку — GC работает непредсказуемо. Не строй логику на предположении, что объект будет собран в конкретный момент.
- Передавать сам объект как heldValue в FinalizationRegistry —
registry.register(obj, obj)создаёт сильную ссылку через heldValue — obj никогда не будет собран! - WeakRef вместо WeakMap — если нужно хранить данные, связанные с объектом, WeakMap проще и надёжнее WeakRef + кэш. WeakRef нужен в специфических сценариях.