Kolekcje: WeakSet i WeakMap

Javascript
Priorytet: Normalny Szkic

Zadanie 5.2: Kolekcje Słabe (Weak Collections)

Wstęp

JavaScript automatycznie zarządza pamięcią (Garbage Collection). Kiedy coś nie jest już potrzebne, jest usuwane z pamięci. Jednak "zwykłe" Set i Map trzymają "silne" referencje do swoich elementów – dopóki element jest w kolekcji, nie zostanie usunięty z pamięci, nawet jeśli nigdzie indziej w programie nie jest używany.

WeakSet i WeakMap trzymają "słabe" referencje. Jeśli obiekt istnieje TYLKO w WeakMap, Garbage Collector może go usunąć.

Cel zadania

Zrozumienie różnic między silnymi a słabymi referencjami oraz zastosowanie WeakMap do przechowywania prywatnych danych obiektów.

Wymagania techniczne

  1. Stworzenie WeakMap.
  2. Zrozumienie ograniczeń (kluczem musi być obiekt, brak metod iteracyjnych).
  3. Symulacja usuwania obiektu z pamięci (teoretyczna).

Kroki do wykonania

1. WeakMap vs Map

Główne różnice:

  1. Kluczem w WeakMap (i elementem w WeakSet) musi być obiekt. Nie może to być liczba ani napis.
  2. WeakMap nie jest iterowalna (brak forEach, size, entries).

Stwórz plik weak.js:

let user = { name: "Jan" };

const strongMap = new Map();
const weakMap = new WeakMap();

strongMap.set(user, "Dane Jana");
weakMap.set(user, "Dane Jana (Weak)");

console.log(strongMap.get(user)); // "Dane Jana"
console.log(weakMap.get(user));   // "Dane Jana (Weak)"

// Scenariusz: Użytkownik wylogowuje się (usuwamy referencję)
user = null;

// W tym momencie:
// W strongMap dane nadal istnieją (Garbage Collector ich nie ruszy)
// W weakMap dane zostaną usunięte automatycznie (przy najbliższym czyszczeniu pamięci)

2. Praktyczne zastosowanie: Prywatne dane

Często chcemy przypisać dodatkowe dane do obiektu (np. DOM elementu), ale nie chcemy modyfikować samego obiektu ani martwić się o wycieki pamięci, gdy element zostanie usunięty ze strony.

// Symulacja licznika odwiedzin dla obiektów użytkowników
const visitCount = new WeakMap();

function countUser(userObj) {
    let count = visitCount.get(userObj) || 0;
    visitCount.set(userObj, count + 1);
}

let user1 = { id: 1 };
countUser(user1);
console.log(visitCount.get(user1)); // 1

3. Zadanie dla chętnych

Stwórz strukturę, która "śledzi" aktywne sesje użytkowników. Jeśli obiekt sesji zostanie usunięty (np. przez pomyłkę lub timeout), WeakMap powinna przestać go pamiętać. (Uwaga: W JS trudno "zobaczyć" moment działania Garbage Collectora w konsoli, ale ważna jest sama koncepcja).

[!TIP] Używaj WeakMap tam, gdzie kluczami są obiekty, które mogą zniknąć w trakcie działania aplikacji (np. elementy HTML, tymczasowe instancje klas).

[!IMPORTANT] Commit: Zadanie 5.2 - WeakSet i WeakMap.

4. Więcej ćwiczeń (Samodzielne)

Ćwiczenie A: Cache Metadanych (WeakMap)

Wyobraź sobie system gry RPG. Masz obiekty reprezentujące postacie (character). Chcesz przechowywać dla nich tymczasowe statusy (np. "zatrucie", "błogosławieństwo"), ale nie chcesz "brudzić" głównego obiektu postaci dodatkowymi polami.

  1. Stwórz klasę lub obiekt fabrykujący Character.
  2. Stwórz WeakMap o nazwie statusEffects.
  3. Napisz funkcję applyStatus(character, status), która dodaje status do WeakMap.
  4. Symulacja: Stwórz postać, nadaj jej status, sprawdź czy status istnieje. Następnie usuń postać (ustaw zmienną na null) i uświadom sobie, że wpis w statusEffects też zostanie posprzątany przez GC.

Ćwiczenie B: Oznaczanie przetworzonych wiadomości (WeakSet)

Masz system, który przetwarza wiadomości (obiekty). Każda wiadomość może być przetworzona tylko raz.

  1. Stwórz WeakSet o nazwie processedMessages.
  2. Stwórz tablicę obiektów messages (np. {id: 1, text: "Witam"}, {id: 2, text: "Żegnam"}).
  3. Napisz funkcję process(message), która:
    • Sprawdza, czy wiadomość jest w processedMessages.
    • Jeśli tak -> console.log("Już było!").
    • Jeśli nie -> console.log("Przetwarzam...") i dodaje ją do zbioru.
  4. Przetestuj funkcję wywołując ją dwa razy na tej samej wiadomości.
  5. Dlaczego WeakSet jest tu lepszy niż Set? Bo gdy tablica messages zostanie wyczyszczona, historia przetworzeń też zniknie z pamięci, nie blokując zasobów.