← Назад к вопросам

Что такое Weak Reference в JavaScript?

2.8 Senior🔥 71 комментариев
#Node.js и JavaScript#Алгоритмы и структуры данных

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Weak References в JavaScript: управление памятью

Что такое Weak Reference

Weak Reference это тип ссылки на объект, который НЕ предотвращает его удаление garbage collector'ом. Это в отличие от обычных (strong) ссылок, которые заставляют объект оставаться в памяти.

Strong vs Weak References

// Strong Reference — объект остаётся в памяти
const user = { name: 'John', id: 1 };
console.log(user); // { name: 'John', id: 1 }
// Даже если никто не использует этот объект, он остаётся

// Когда сборка мусора удалит этот объект?
// НИКОГДА пока есть хотя бы одна strong reference!

// Weak Reference — объект может быть удалён
const weakRef = new WeakRef(user);
// Если на объект нет других ссылок, GC может его удалить

WeakRef

WeakRef позволяет иметь слабую ссылку на объект:

let user = { name: 'John', id: 1 };
const weakRef = new WeakRef(user);

console.log(weakRef.deref()); // { name: 'John', id: 1 } — объект ещё в памяти

user = null; // Удаляем strong reference

// После garbage collection:
console.log(weakRef.deref()); // undefined — объект был удалён

Практический пример: Кэш с автоочисткой

// Плохо: кэш растёт бесконечно
const cache = new Map();

function cacheUser(id, user) {
  cache.set(id, user);
}

function getUser(id) {
  return cache.get(id);
}

// Проблема: кэш никогда не очищается, даже если никто не нужен
cacheUser(1, { name: 'John' });
cacheUser(2, { name: 'Jane' });
// ... через год в кэше 1 миллион пользователей, приложение ест 10GB памяти

// Хорошо: используем WeakMap для автоматической очистки
const weakCache = new WeakMap();

function cacheUserWeak(user) {
  // Используем сам user как ключ
  weakCache.set(user, { cached: true, timestamp: Date.now() });
}

function getCachedData(user) {
  return weakCache.get(user);
}

// Когда user удалится из памяти, автоматически удалится и из кэша
let user = { id: 1, name: 'John' };
cacheUserWeak(user);
console.log(getCachedData(user)); // { cached: true, timestamp: ... }

user = null; // удаляем ссылку
// GC удалит user, и автоматически удалит его из weakCache

WeakMap

WeakMap это Map с weak references только для ключей:

const user1 = { id: 1, name: 'John' };
const user2 = { id: 2, name: 'Jane' };

const weakMap = new WeakMap();
weakMap.set(user1, { metadata: 'some data' });
weakMap.set(user2, { metadata: 'other data' });

console.log(weakMap.get(user1)); // { metadata: 'some data' }

// Удаляем ссылку на user1
user1 = null;

// После GC:
console.log(weakMap.get(user1)); // undefined
console.log(weakMap.size); // Error! WeakMap не имеет .size

// Почему нет .size? Потому что ключи могут быть удалены в любой момент

WeakSet

WeakSet это Set с weak references:

const weakSet = new WeakSet();
const obj1 = { id: 1 };
const obj2 = { id: 2 };

weakSet.add(obj1);
weakSet.add(obj2);

console.log(weakSet.has(obj1)); // true

obj1 = null;
// После GC:
console.log(weakSet.has(obj1)); // false (obj1 был удалён)

Реальный пример: Отслеживание активных пользователей

// Проблема: памеяь растёт бесконечно
const activeUsers = new Set(); // strong references!

function userLoggedIn(user) {
  activeUsers.add(user);
}

function userLoggedOut(user) {
  activeUsers.delete(user); // Должны явно удалить
}

function isUserActive(user) {
  return activeUsers.has(user);
}

// Проблема: если забыть вызвать userLoggedOut, user останется в памяти навсегда!

// Решение: используем WeakSet
const activeUsersWeak = new WeakSet();

function userLoggedInWeak(user) {
  activeUsersWeak.add(user);
}

function isUserActiveWeak(user) {
  return activeUsersWeak.has(user);
}

// Когда user объект удалится откуда-то, автоматически удалится из WeakSet
let user = getUserFromDB();
userLoggedInWeak(user);
console.log(isUserActiveWeak(user)); // true

user = null; // удаляем ссылку
// GC удалит user, и автоматически из activeUsersWeak

FinalizationRegistry — вызов функции при удалении

// FinalizationRegistry позволяет запустить callback когда объект удалится

const registry = new FinalizationRegistry((heldValue) => {
  console.log(`Объект был удалён, cleanup value: ${heldValue}`);
});

let obj = { id: 1, data: 'large data' };
const cleanupValue = 'user-123';

// Регистрируем obj для отслеживания
registry.register(obj, cleanupValue);

// После удаления obj:
obj = null;
// GC запустит callback с cleanupValue
// "Объект был удалён, cleanup value: user-123"

Пример: Отслеживание открытых файлов

import { FinalizationRegistry } from 'node:finalization';

const openFiles = new FinalizationRegistry((filename) => {
  console.log(`Закрываю файл: ${filename}`);
  // fs.closeSync(filename);
});

class FileHandle {
  constructor(filename) {
    this.filename = filename;
    // Register для автоматического закрытия
    openFiles.register(this, filename);
  }

  read() {
    return readFileSync(this.filename);
  }
}

let file = new FileHandle('data.txt');
console.log(file.read()); // читаем

file = null; // забыли закрыть
// GC автоматически вызовет cleanup и закроет файл

Когда использовать WeakMap/WeakSet

✅ Хорошие use cases:
- Metadata для DOM элементов
- Кэширование данных по объектам
- Отслеживание активных сессий
- Private данные для объектов
- Listener registry без утечек памяти

❌ Не подходит для:
- Хранение primitives (number, string, symbol)
  WeakMap.set(123, value); // TypeError!
- Когда нужно итерировать (нет методов итерации)
- Когда нужен .size или .length

Пример: Private данные в классах (до #)

// Раньше (ES2020 до приватных полей):
const privateData = new WeakMap();

class User {
  constructor(name, password) {
    this.name = name;
    privateData.set(this, { password });
  }

  checkPassword(pwd) {
    return privateData.get(this).password === pwd;
  }
}

const user = new User('John', 'secret123');
console.log(user.password); // undefined — скрыто!
console.log(user.checkPassword('secret123')); // true

// Если user удалится, privateData автоматически очистится

// Сейчас (ES2022 приватные поля):
class UserModern {
  #password; // приватное поле

  constructor(name, password) {
    this.name = name;
    this.#password = password;
  }

  checkPassword(pwd) {
    return this.#password === pwd;
  }
}

Важные нюансы

// WeakMap/WeakSet могут содержать только ОБЪЕКТЫ
const wm = new WeakMap();
const key = 'string'; // string

wm.set(key, 'value'); // TypeError!
// Ключи должны быть объектами

const validKey = { id: 1 }; // объект
wm.set(validKey, 'value'); // OK

// Нет методов итерации
const arr = [1, 2, 3];
for (const item of arr) { } // OK

const ws = new WeakSet([obj1, obj2]);
for (const item of ws) { } // TypeError! Нельзя итерировать

// Нет методов size, length
console.log(ws.size); // undefined
console.log(wm.length); // undefined

Мой опыт использования

В production используем:

  • WeakMap для кэширования в больших приложениях
  • WeakSet для отслеживания активных ресурсов
  • FinalizationRegistry в редких случаях для cleanup
  • # приватные поля вместо WeakMap (ES2022+)

Основная идея: если объект нужен только пока существуют другие ссылки, используй weak references.