86. WeakRef и FinalizationRegistry
WeakRef и FinalizationRegistry — инструменты для работы с памятью в JavaScript. Они позволяют держать «слабую» ссылку на объект (не мешая сборщику мусора его удалить) и выполнять код при удалении объекта.
⚠️ Осторожно: Используйте эти API только когда реально необходимо управлять памятью. Сборщик мусора непредсказуем — не полагайтесь на точное время удаления объектов.
WeakRef — слабая ссылка
Заголовок раздела «WeakRef — слабая ссылка»Обычная ссылка предотвращает сборку мусора. WeakRef — нет:
let target = { name: 'Тяжёлый объект', data: new Array(1000000) }
const weakRef = new WeakRef(target)
// Получить объект через WeakRefconsole.log(weakRef.deref()) // { name: 'Тяжёлый объект', ... }console.log(weakRef.deref()?.name) // 'Тяжёлый объект'
// Удаляем сильную ссылкуtarget = null
// Теперь объект может быть собран GC// weakRef.deref() вернёт undefined после сборкиsetTimeout(() => { const obj = weakRef.deref() if (obj) { console.log('Объект ещё жив:', obj.name) } else { console.log('Объект был удалён сборщиком мусора') }}, 1000)Кэш с WeakRef
Заголовок раздела «Кэш с WeakRef»Классический случай применения — кэш, который не утечёт памятью:
class WeakCache { #cache = new Map()
set(key, value) { this.#cache.set(key, new WeakRef(value)) }
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 }
has(key) { return this.get(key) !== undefined }}
const cache = new WeakCache()
let bigData = { id: 1, data: new Array(100000).fill('x') }cache.set('bigData', bigData)
console.log(cache.get('bigData')?.id) // 1
bigData = null // позволяем GC удалить объект// После GC: cache.get('bigData') === undefinedFinalizationRegistry — колбэк при удалении
Заголовок раздела «FinalizationRegistry — колбэк при удалении»FinalizationRegistry вызывает колбэк когда объект удалён сборщиком мусора:
const registry = new FinalizationRegistry((heldValue) => { console.log(`Объект удалён! Метка: ${heldValue}`)})
let obj1 = { name: 'Объект А' }let obj2 = { name: 'Объект Б' }
// Регистрируем объекты с метками (heldValue)registry.register(obj1, 'А')registry.register(obj2, 'Б')
// Удаляем ссылкиobj1 = null // когда GC удалит → 'Объект удалён! Метка: А'obj2 = null // когда GC удалит → 'Объект удалён! Метка: Б'Комбинация WeakRef + FinalizationRegistry
Заголовок раздела «Комбинация WeakRef + FinalizationRegistry»Реальный паттерн — кэш с автоочисткой при сборке мусора:
class ManagedCache { #cache = new Map() #registry = new FinalizationRegistry((key) => { // Вызывается когда значение удалено GC this.#cache.delete(key) console.log(`Кэш очищен для ключа: ${key}`) })
set(key, value) { this.#registry.register(value, key) this.#cache.set(key, new WeakRef(value)) }
get(key) { return this.#cache.get(key)?.deref() }
get size() { return this.#cache.size }}
const cache = new ManagedCache()
// Тяжёлые объектыcache.set('user:1', { id: 1, data: 'много данных' })cache.set('user:2', { id: 2, data: 'ещё данных' })
console.log(cache.get('user:1')?.id) // 1console.log('Размер кэша:', cache.size) // 2Отмена регистрации
Заголовок раздела «Отмена регистрации»const registry = new FinalizationRegistry((label) => { console.log('Удалён:', label)})
let obj = { data: 'важные данные' }
// Сохраняем токен для отменыconst token = {}registry.register(obj, 'мой-объект', token)
// Можно отменить регистрацию (колбэк не вызовется)registry.unregister(token)
obj = null // колбэк не вызоветсяКогда использовать
Заголовок раздела «Когда использовать»✅ Подходит для:
- Кэши с тяжёлыми объектами (изображения, большие данные)
- Отладочные инструменты для отслеживания утечек памяти
- Освобождение нативных ресурсов (файловые дескрипторы, соединения)
❌ Не использовать для:
- Критически важной логики (GC непредсказуем)
- Замены обычного управления ресурсами
- Синхронной очистки (колбэк асинхронный и может не вызваться)