Перейти к содержимому

86. WeakRef и FinalizationRegistry

WeakRef и FinalizationRegistry — инструменты для работы с памятью в JavaScript. Они позволяют держать «слабую» ссылку на объект (не мешая сборщику мусора его удалить) и выполнять код при удалении объекта.

⚠️ Осторожно: Используйте эти API только когда реально необходимо управлять памятью. Сборщик мусора непредсказуем — не полагайтесь на точное время удаления объектов.

Обычная ссылка предотвращает сборку мусора. WeakRef — нет:

let target = { name: 'Тяжёлый объект', data: new Array(1000000) }
const weakRef = new WeakRef(target)
// Получить объект через WeakRef
console.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)

Классический случай применения — кэш, который не утечёт памятью:

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') === undefined

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 удалит → 'Объект удалён! Метка: Б'

Реальный паттерн — кэш с автоочисткой при сборке мусора:

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) // 1
console.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 непредсказуем)
  • Замены обычного управления ресурсами
  • Синхронной очистки (колбэк асинхронный и может не вызваться)