JavaScript WeakMap ve WeakSet: Hafıza Yönetimi

JavaScript WeakMap ve WeakSet: Hafıza Yönetimi
JavaScript'te hafıza yönetimi, performanslı uygulamalar geliştirmenin kritik bir parçasıdır. WeakMap ve WeakSet, nesnelere zayıf referanslar tutarak garbage collector'ın gereksiz verileri temizlemesine izin verir. Bu rehberde WeakMap ve WeakSet'in çalışma prensiplerini, kullanım senaryolarını, Map/Set ile farklılıklarını ve pratik örneklerini inceleyeceğiz.
Zayıf Referans (Weak Reference) Nedir?
Normal bir referans, bir nesneye işaret ettiği sürece garbage collector o nesneyi temizleyemez. Zayıf referans ise nesnenin garbage collection'a engel olmaz:
// Güçlü referans: obj bellekte kalır
let obj = { data: 'önemli veri' };
const map = new Map();
map.set(obj, 'değer');
obj = null; // obj hala Map içinde referans edildiği için bellekte kalır
// Zayıf referans: obj temizlenebilir
let obj2 = { data: 'geçici veri' };
const weakMap = new WeakMap();
weakMap.set(obj2, 'değer');
obj2 = null; // obj2'ye başka referans kalmadığı için GC temizleyebilir
WeakMap Detaylı İnceleme
WeakMap vs Map Karşılaştırma
| Özellik | Map | WeakMap |
|---|---|---|
| Anahtar tipi | Her tip | Sadece object/symbol |
| Enumerable | Evet (for...of, forEach) | Hayır |
| size özelliği | Var | Yok |
| clear() metodu | Var | Yok |
| GC etkisi | Engeller | Engellemez |
| Iterasyon | keys(), values(), entries() | Yok |
WeakMap API
const weakMap = new WeakMap();
const key1 = { id: 1 };
const key2 = { id: 2 };
// Temel işlemler
weakMap.set(key1, 'değer 1');
weakMap.set(key2, { nested: 'data' });
console.log(weakMap.get(key1)); // 'değer 1'
console.log(weakMap.has(key2)); // true
weakMap.delete(key1);
console.log(weakMap.has(key1)); // false
// Primitive anahtarlar kullanılamaz
// weakMap.set('string', 'value'); // TypeError!
// weakMap.set(42, 'value'); // TypeError!
WeakMap Kullanım Senaryoları
1. Private Data (Özel Veri Saklama)
// WeakMap ile private field simülasyonu
const privateData = new WeakMap();
class Person {
constructor(name, age, ssn) {
this.name = name;
this.age = age;
// SSN'yi private olarak sakla
privateData.set(this, { ssn });
}
getSSN(authToken) {
if (!isValidToken(authToken)) {
throw new Error('Yetkisiz erişim');
}
return privateData.get(this)?.ssn;
}
}
const person = new Person('Ali', 30, '123-45-6789');
console.log(person.name); // 'Ali'
console.log(person.ssn); // undefined (erişilemez)
// person GC tarafından temizlendiğinde SSN verisi de otomatik silinir
2. DOM Element Metadata
// DOM elementlerine metadata ekle (hafıza sızıntısı riski olmadan)
const elementData = new WeakMap();
function trackElement(element, metadata) {
elementData.set(element, {
...metadata,
trackedAt: Date.now(),
});
}
function getElementData(element) {
return elementData.get(element);
}
// Kullanım
const button = document.querySelector('#submit-btn');
trackElement(button, { clicks: 0, lastClicked: null });
button.addEventListener('click', () => {
const data = getElementData(button);
if (data) {
data.clicks++;
data.lastClicked = Date.now();
}
});
// button DOM'dan kaldırıldığında, metadata otomatik GC tarafından temizlenir
3. Cache / Memoization
// Nesne bazlı cache (hafıza dostu)
const cache = new WeakMap();
function expensiveComputation(obj) {
if (cache.has(obj)) {
console.log('Cache hit');
return cache.get(obj);
}
console.log('Computing...');
const result = {
hash: JSON.stringify(obj).split('').reduce(
(a, b) => ((a << 5) - a + b.charCodeAt(0)) | 0, 0
),
timestamp: Date.now(),
};
cache.set(obj, result);
return result;
}
let data = { name: 'test', values: [1, 2, 3] };
expensiveComputation(data); // 'Computing...'
expensiveComputation(data); // 'Cache hit'
data = null; // Cache otomatik temizlenir
WeakSet Detaylı İnceleme
const weakSet = new WeakSet();
const obj1 = { id: 1 };
const obj2 = { id: 2 };
weakSet.add(obj1);
weakSet.add(obj2);
console.log(weakSet.has(obj1)); // true
weakSet.delete(obj2);
console.log(weakSet.has(obj2)); // false
WeakSet Kullanım Senaryoları
// 1. Circular Reference Tespiti
function deepClone(obj, seen = new WeakSet()) {
if (obj === null || typeof obj !== 'object') return obj;
// Circular reference kontrolü
if (seen.has(obj)) {
throw new Error('Circular reference tespit edildi');
}
seen.add(obj);
if (Array.isArray(obj)) {
return obj.map(item => deepClone(item, seen));
}
const clone = {};
for (const key of Object.keys(obj)) {
clone[key] = deepClone(obj[key], seen);
}
return clone;
}
// 2. Nesne İşlenme Takibi
const processed = new WeakSet();
function processOnce(item) {
if (processed.has(item)) {
console.log('Zaten işlendi, atlanıyor');
return;
}
// İşle
console.log('İşleniyor:', item);
processed.add(item);
}
const task = { id: 1, name: 'görev' };
processOnce(task); // 'İşleniyor: ...'
processOnce(task); // 'Zaten işlendi, atlanıyor'
// 3. Branding / Tagging
const verified = new WeakSet();
function verifyUser(user) {
// Doğrulama mantığı...
verified.add(user);
return user;
}
function isVerified(user) {
return verified.has(user);
}
const user = verifyUser({ name: 'Ali', email: 'ali@test.com' });
console.log(isVerified(user)); // true
WeakRef ve FinalizationRegistry
ES2021 ile gelen WeakRef ve FinalizationRegistry, daha detaylı zayıf referans kontrolü sağlar:
// WeakRef: Nesneye zayıf referans
let target = { data: 'önemli' };
const weakRef = new WeakRef(target);
console.log(weakRef.deref()); // { data: 'önemli' }
target = null;
// GC çalıştıktan sonra:
// weakRef.deref() => undefined
// FinalizationRegistry: GC temizliği geri bildirimi
const registry = new FinalizationRegistry((heldValue) => {
console.log(`Nesne temizlendi: ${heldValue}`);
// Temizlik işlemleri (cache silme, bağlantı kapatma vb.)
});
let obj = { name: 'geçici' };
registry.register(obj, 'obj-temizlik-bilgisi');
obj = null; // GC çalıştığında: "Nesne temizlendi: obj-temizlik-bilgisi"
WeakRef ve FinalizationRegistry ileri düzey araçlardır. GC zamanlaması garanti edilmez, bu nedenle kritik iş mantığını bunlara dayandırmayın. Çoğu senaryoda WeakMap/WeakSet yeterlidir.
Performans ve Hafıza Karşılaştırması
// Hafıza sızıntısı örneği (Map ile)
function leakyCache() {
const cache = new Map();
let counter = 0;
return function process(data) {
const key = { id: counter++ };
cache.set(key, heavyComputation(data));
// key referansı hiç temizlenmiyor = hafıza sızıntısı!
return cache.get(key);
};
}
// Güvenli versiyon (WeakMap ile)
function safeCache() {
const cache = new WeakMap();
return function process(obj) {
if (!cache.has(obj)) {
cache.set(obj, heavyComputation(obj));
}
return cache.get(obj);
};
}
Sonuç
WeakMap ve WeakSet, JavaScript'te hafıza yönetiminin güçlü araçlarıdır. Nesnelere metadata ekleme, cache mekanizması, private data saklama ve circular reference tespiti gibi senaryolarda hafıza sızıntısı riskini ortadan kaldırırlar. Enumerable olmamaları ve primitive anahtar desteklememeleri sınırlılıklarıdır, ancak doğru kullanıldıklarında uygulamanızın hafıza verimliliğini önemli ölçüde artırırlar.