← Назад к вопросам
Что такое 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.