İçeriğe geç
JavaScript

JavaScript WeakMap ve WeakSet: Hafıza Yönetimi

Tarık Tunç
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

ÖzellikMapWeakMap
Anahtar tipiHer tipSadece object/symbol
EnumerableEvet (for...of, forEach)Hayır
size özelliğiVarYok
clear() metoduVarYok
GC etkisiEngellerEngellemez
Iterasyonkeys(), 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
Bilgi: WeakMap tabanlı cache, nesne artık kullanılmadığında otomatik olarak temizlenir. Bu sayede manuel cache invalidation'a gerek kalmaz ve hafıza sızıntısı riski ortadan kalkar.

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"
Uyarı: 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);
  };
}
İpucu: Chrome DevTools Memory sekmesinden heap snapshot alarak WeakMap/WeakSet'in hafıza üzerindeki etkisini gözlemleyebilirsiniz. Retained size farkını karşılaştırın.

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.

Kaynaklar

İlgili Yazılar