В React каждый DOM-элемент может иметь связанные данные (обработчики, состояние). Хранить их в обычном Map опасно — элемент удалится из DOM, но данные останутся в памяти навсегда. WeakMap решает это: когда элемент удаляется, связанные данные автоматически очищаются сборщиком мусора.
Если объект хранится в обычном Map или Set, он никогда не будет удалён сборщиком мусора, даже если нигде больше не используется. WeakMap и WeakSet хранят «слабые» ссылки — они не препятствуют сборке мусора.
let user = { name: 'Иван' }
const cache = new Map()
cache.set(user, { data: 'кешированные данные' })
user = null // хотим удалить объект
// НО: Map хранит сильную ссылку — объект остаётся в памяти!
// cache ссылается на { name: 'Иван' } — GC не удалит егоВ WeakMap ключами могут быть только объекты (не примитивы). Ссылка «слабая» — если объект-ключ нигде больше не используется, GC удаляет его вместе с данными в WeakMap:
let user = { name: 'Иван' }
const cache = new WeakMap()
cache.set(user, { data: 'некие данные' })
user = null
// Теперь объект может быть удалён GC
// Запись в WeakMap исчезнет автоматически — утечки памяти нет!const wm = new WeakMap()
const key = {}
wm.set(key, 'value')
wm.get(key) // 'value'
wm.has(key) // true
wm.delete(key) // true
// Чего НЕТ в WeakMap:
// wm.size — нет (нельзя знать сколько записей)
// wm.keys() — нельзя итерировать
// wm.forEach() — нет перебораОтсутствие итерации — намеренное: нельзя получить список ключей, которые уже могут быть удалены GC.
const _private = new WeakMap()
class BankAccount {
constructor(owner, balance) {
_private.set(this, { balance, transactions: [] })
this.owner = owner
}
deposit(amount) {
const data = _private.get(this)
data.balance += amount
data.transactions.push({ type: 'deposit', amount })
}
get balance() {
return _private.get(this).balance
}
}
const acc = new BankAccount('Иван', 1000)
acc.deposit(500)
console.log(acc.balance) // 1500
console.log(acc._private) // undefined — данные недоступны снаружи!const cache = new WeakMap()
function getMetadata(element) {
if (!cache.has(element)) {
cache.set(element, computeExpensiveData(element))
}
return cache.get(element)
}
// Когда element удалится из DOM — данные автоматически освободятсяWeakSet хранит только объекты, без дублей, не итерируем:
const processed = new WeakSet()
function processRequest(req) {
if (processed.has(req)) return 'уже обработан'
processed.add(req)
// ... обработка
return 'обработан'
}
// Когда объект req удаляется — запись в WeakSet исчезает самаОшибка 1: попытка использовать примитив как ключ WeakMap
const wm = new WeakMap()
wm.set('строка', 'значение') // TypeError: Invalid value used as weak map key
wm.set(42, 'значение') // TypeError!
wm.set({}, 'значение') // OK — объект
// WeakMap принимает только объекты как ключиОшибка 2: попытка проитерировать WeakMap
const wm = new WeakMap()
wm.set({}, 1)
wm.size // undefined — не существует
for (const k of wm) {} // TypeError: wm is not iterable
// Это намеренное ограничение — используй Map если нужна итерацияОшибка 3: использование WeakMap вместо Map когда нужны примитивные ключи
// Неправильно — userId это строка, не объект
const userCache = new WeakMap()
userCache.set('user-123', data) // TypeError!
// Правильно — для примитивных ключей используй Map
const userCache2 = new Map()
userCache2.set('user-123', data) // OKWeakMap для приватных данных банковского счёта и кеш вычислений
// 1. WeakMap как хранилище приватных данных экземпляров
const _private = new WeakMap()
class BankAccount {
constructor(owner, balance) {
_private.set(this, { balance, transactions: [] })
this.owner = owner
}
deposit(amount) {
if (amount <= 0) throw new Error('Сумма должна быть положительной')
const data = _private.get(this)
data.balance += amount
data.transactions.push({ type: 'deposit', amount, date: new Date().toLocaleDateString() })
}
withdraw(amount) {
const data = _private.get(this)
if (amount > data.balance) throw new Error('Недостаточно средств')
data.balance -= amount
data.transactions.push({ type: 'withdraw', amount, date: new Date().toLocaleDateString() })
}
get balance() { return _private.get(this).balance }
get transactions() { return [..._private.get(this).transactions] }
}
const acc = new BankAccount('Иван', 1000)
acc.deposit(500)
acc.withdraw(200)
console.log(acc.balance) // 1300
console.log(acc.transactions.length) // 2
console.log(acc._private) // undefined — данные скрыты!
try {
acc.withdraw(5000) // больше баланса
} catch (e) {
console.log(e.message) // 'Недостаточно средств'
}
// 2. WeakMap как кеш — не блокирует сборку мусора
const computeCache = new WeakMap()
function getCachedStats(obj) {
if (computeCache.has(obj)) {
console.log('Из кеша')
return computeCache.get(obj)
}
// Имитация сложного вычисления
const stats = {
keyCount: Object.keys(obj).length,
hasNested: Object.values(obj).some(v => typeof v === 'object' && v !== null)
}
computeCache.set(obj, stats)
return stats
}
const config = { host: 'localhost', port: 3000, db: { name: 'mydb' } }
console.log(getCachedStats(config)) // { keyCount: 3, hasNested: true }
console.log(getCachedStats(config)) // Из кеша: { keyCount: 3, hasNested: true }В React каждый DOM-элемент может иметь связанные данные (обработчики, состояние). Хранить их в обычном Map опасно — элемент удалится из DOM, но данные останутся в памяти навсегда. WeakMap решает это: когда элемент удаляется, связанные данные автоматически очищаются сборщиком мусора.
Если объект хранится в обычном Map или Set, он никогда не будет удалён сборщиком мусора, даже если нигде больше не используется. WeakMap и WeakSet хранят «слабые» ссылки — они не препятствуют сборке мусора.
let user = { name: 'Иван' }
const cache = new Map()
cache.set(user, { data: 'кешированные данные' })
user = null // хотим удалить объект
// НО: Map хранит сильную ссылку — объект остаётся в памяти!
// cache ссылается на { name: 'Иван' } — GC не удалит егоВ WeakMap ключами могут быть только объекты (не примитивы). Ссылка «слабая» — если объект-ключ нигде больше не используется, GC удаляет его вместе с данными в WeakMap:
let user = { name: 'Иван' }
const cache = new WeakMap()
cache.set(user, { data: 'некие данные' })
user = null
// Теперь объект может быть удалён GC
// Запись в WeakMap исчезнет автоматически — утечки памяти нет!const wm = new WeakMap()
const key = {}
wm.set(key, 'value')
wm.get(key) // 'value'
wm.has(key) // true
wm.delete(key) // true
// Чего НЕТ в WeakMap:
// wm.size — нет (нельзя знать сколько записей)
// wm.keys() — нельзя итерировать
// wm.forEach() — нет перебораОтсутствие итерации — намеренное: нельзя получить список ключей, которые уже могут быть удалены GC.
const _private = new WeakMap()
class BankAccount {
constructor(owner, balance) {
_private.set(this, { balance, transactions: [] })
this.owner = owner
}
deposit(amount) {
const data = _private.get(this)
data.balance += amount
data.transactions.push({ type: 'deposit', amount })
}
get balance() {
return _private.get(this).balance
}
}
const acc = new BankAccount('Иван', 1000)
acc.deposit(500)
console.log(acc.balance) // 1500
console.log(acc._private) // undefined — данные недоступны снаружи!const cache = new WeakMap()
function getMetadata(element) {
if (!cache.has(element)) {
cache.set(element, computeExpensiveData(element))
}
return cache.get(element)
}
// Когда element удалится из DOM — данные автоматически освободятсяWeakSet хранит только объекты, без дублей, не итерируем:
const processed = new WeakSet()
function processRequest(req) {
if (processed.has(req)) return 'уже обработан'
processed.add(req)
// ... обработка
return 'обработан'
}
// Когда объект req удаляется — запись в WeakSet исчезает самаОшибка 1: попытка использовать примитив как ключ WeakMap
const wm = new WeakMap()
wm.set('строка', 'значение') // TypeError: Invalid value used as weak map key
wm.set(42, 'значение') // TypeError!
wm.set({}, 'значение') // OK — объект
// WeakMap принимает только объекты как ключиОшибка 2: попытка проитерировать WeakMap
const wm = new WeakMap()
wm.set({}, 1)
wm.size // undefined — не существует
for (const k of wm) {} // TypeError: wm is not iterable
// Это намеренное ограничение — используй Map если нужна итерацияОшибка 3: использование WeakMap вместо Map когда нужны примитивные ключи
// Неправильно — userId это строка, не объект
const userCache = new WeakMap()
userCache.set('user-123', data) // TypeError!
// Правильно — для примитивных ключей используй Map
const userCache2 = new Map()
userCache2.set('user-123', data) // OKWeakMap для приватных данных банковского счёта и кеш вычислений
// 1. WeakMap как хранилище приватных данных экземпляров
const _private = new WeakMap()
class BankAccount {
constructor(owner, balance) {
_private.set(this, { balance, transactions: [] })
this.owner = owner
}
deposit(amount) {
if (amount <= 0) throw new Error('Сумма должна быть положительной')
const data = _private.get(this)
data.balance += amount
data.transactions.push({ type: 'deposit', amount, date: new Date().toLocaleDateString() })
}
withdraw(amount) {
const data = _private.get(this)
if (amount > data.balance) throw new Error('Недостаточно средств')
data.balance -= amount
data.transactions.push({ type: 'withdraw', amount, date: new Date().toLocaleDateString() })
}
get balance() { return _private.get(this).balance }
get transactions() { return [..._private.get(this).transactions] }
}
const acc = new BankAccount('Иван', 1000)
acc.deposit(500)
acc.withdraw(200)
console.log(acc.balance) // 1300
console.log(acc.transactions.length) // 2
console.log(acc._private) // undefined — данные скрыты!
try {
acc.withdraw(5000) // больше баланса
} catch (e) {
console.log(e.message) // 'Недостаточно средств'
}
// 2. WeakMap как кеш — не блокирует сборку мусора
const computeCache = new WeakMap()
function getCachedStats(obj) {
if (computeCache.has(obj)) {
console.log('Из кеша')
return computeCache.get(obj)
}
// Имитация сложного вычисления
const stats = {
keyCount: Object.keys(obj).length,
hasNested: Object.values(obj).some(v => typeof v === 'object' && v !== null)
}
computeCache.set(obj, stats)
return stats
}
const config = { host: 'localhost', port: 3000, db: { name: 'mydb' } }
console.log(getCachedStats(config)) // { keyCount: 3, hasNested: true }
console.log(getCachedStats(config)) // Из кеша: { keyCount: 3, hasNested: true }Реализуй функцию createPrivateStore() которая использует WeakMap для хранения приватных данных объектов. Метод set(obj, key, value) сохраняет значение, get(obj, key) читает, has(obj, key) проверяет наличие, delete(obj, key) удаляет конкретный ключ.
set: store.get(obj)[key] = value. get: if (!store.has(obj)) return undefined; return store.get(obj)[key]. has: store.has(obj) && key in store.get(obj).