WeakMap a WeakSet

Jak víme z kapitoly Garbage collection, JavaScript engine uchovává hodnotu v paměti, dokud je „dosažitelná“ a může být potenciálně použita.

Například:

let john = { name: "John" };

// the object can be accessed, john is the reference to it

// overwrite the reference
john = null;

// the object will be removed from memory

Vlastnosti objektu nebo prvků pole nebo jiné datové struktury jsou obvykle považovány za dosažitelné a uchovávané v paměti, dokud je tato datová struktura v paměti.

Pokud například vložíme objekt do pole, pak dokud je pole živé, objekt bude živý také, i když na něj nejsou žádné jiné odkazy.

Takhle:

let john = { name: "John" };

let array = [ john ];

john = null; // overwrite the reference

// the object previously referenced by john is stored inside the array
// therefore it won't be garbage-collected
// we can get it as array[0]

Podobně jako když použijeme objekt jako klíč v běžném Map a poté Map existuje, tento objekt existuje také. Zabírá paměť a nemusí být shromažďovány.

Například:

let john = { name: "John" };

let map = new Map();
map.set(john, "...");

john = null; // overwrite the reference

// john is stored inside the map,
// we can get it by using map.keys()

WeakMap je v tomto ohledu zásadně odlišná. Nebrání to sběru odpadu klíčových objektů.

Podívejme se, co to znamená na příkladech.

Slabá mapa

První rozdíl mezi Map a WeakMap je, že klíče musí být objekty, nikoli primitivní hodnoty:

let weakMap = new WeakMap();

let obj = {};

weakMap.set(obj, "ok"); // works fine (object key)

// can't use a string as the key
weakMap.set("test", "Whoops"); // Error, because "test" is not an object

Nyní, pokud v něm použijeme objekt jako klíč a neexistují žádné další odkazy na tento objekt – bude automaticky odstraněn z paměti (a z mapy).

let john = { name: "John" };

let weakMap = new WeakMap();
weakMap.set(john, "...");

john = null; // overwrite the reference

// john is removed from memory!

Porovnejte to s běžným Map příklad výše. Nyní, pokud john existuje pouze jako klíč WeakMap – bude automaticky odstraněn z mapy (a paměti).

WeakMap nepodporuje iteraci a metody keys() , values() , entries() , takže z něj nelze získat všechny klíče nebo hodnoty.

WeakMap má pouze následující metody:

  • weakMap.get(key)
  • weakMap.set(key, value)
  • weakMap.delete(key)
  • weakMap.has(key)

Proč takové omezení? To z technických důvodů. Pokud objekt ztratil všechny ostatní odkazy (například john ve výše uvedeném kódu), pak má být odpad automaticky sbírán. Technicky však není přesně specifikováno, kdy k čištění dojde .

O tom rozhoduje JavaScript engine. Může se rozhodnout provést vyčištění paměti okamžitě nebo počkat a provést čištění později, když dojde k dalším smazáním. Technicky tedy aktuální počet prvků WeakMap není známo. Motor to možná vyčistil nebo ne, nebo to udělal částečně. Z tohoto důvodu nejsou podporovány metody, které přistupují ke všem klíčům/hodnotám.

Kde tedy takovou datovou strukturu potřebujeme?

Případ použití:další údaje

Hlavní oblast použití pro WeakMap je další úložiště dat .

Pokud pracujeme s objektem, který „patří“ do jiného kódu, možná dokonce do knihovny třetí strany, a chtěli bychom uložit nějaká data s ním spojená, která by měla existovat pouze tehdy, když je objekt naživu – pak WeakMap je přesně to, co je potřeba.

Data vložíme na WeakMap , pomocí objektu jako klíče, a když je objekt shromážděn, tato data automaticky zmizí také.

weakMap.set(john, "secret documents");
// if john dies, secret documents will be destroyed automatically

Podívejme se na příklad.

Máme například kód, který uchovává počet návštěv uživatelů. Informace jsou uloženy v mapě:objekt uživatele je klíč a počet návštěv je hodnota. Když uživatel odejde (jeho objekt dostane odpadky), už nechceme ukládat počet jeho návštěv.

Zde je příklad funkce počítání s Map :

// 📁 visitsCount.js
let visitsCountMap = new Map(); // map: user => visits count

// increase the visits count
function countUser(user) {
 let count = visitsCountMap.get(user) || 0;
 visitsCountMap.set(user, count + 1);
}

A tady je další část kódu, možná jiný soubor, který ji používá:

// 📁 main.js
let john = { name: "John" };

countUser(john); // count his visits

// later john leaves us
john = null;

Nyní john objekt by měl být shromážděn, ale zůstává v paměti, protože je to klíč v visitsCountMap .

Potřebujeme vyčistit visitsCountMap když uživatele odebereme, jinak bude paměť narůstat donekonečna. Takové čištění se může stát únavným úkolem ve složitých architekturách.

Můžeme se tomu vyhnout přechodem na WeakMap místo toho:

// 📁 visitsCount.js
let visitsCountMap = new WeakMap(); // weakmap: user => visits count

// increase the visits count
function countUser(user) {
 let count = visitsCountMap.get(user) || 0;
 visitsCountMap.set(user, count + 1);
}

Nyní již nemusíme čistit visitsCountMap . Po john objekt se stane nedosažitelným všemi prostředky kromě klíče WeakMap , odstraní se z paměti spolu s informacemi pomocí tohoto klíče z WeakMap .

Případ použití:ukládání do mezipaměti

Dalším běžným příkladem je ukládání do mezipaměti. Výsledky funkce můžeme ukládat („cachovat“), takže budoucí volání stejného objektu je mohou znovu použít.

Abychom toho dosáhli, můžeme použít Map (není optimální scénář):

// 📁 cache.js
let cache = new Map();

// calculate and remember the result
function process(obj) {
 if (!cache.has(obj)) {
 let result = /* calculations of the result for */ obj;

 cache.set(obj, result);
 }

 return cache.get(obj);
}

// Now we use process() in another file:

// 📁 main.js
let obj = {/* let's say we have an object */};

let result1 = process(obj); // calculated

// ...later, from another place of the code...
let result2 = process(obj); // remembered result taken from cache

// ...later, when the object is not needed any more:
obj = null;

alert(cache.size); // 1 (Ouch! The object is still in cache, taking memory!)

Pro více volání process(obj) se stejným objektem pouze poprvé vypočítá výsledek a pak jej vezme z cache . Nevýhodou je, že musíme vyčistit cache když objekt již není potřeba.

Pokud nahradíme Map s WeakMap , pak tento problém zmizí. Výsledek uložený v mezipaměti bude automaticky odstraněn z paměti poté, co bude objekt sesbírán.

// 📁 cache.js
let cache = new WeakMap();

// calculate and remember the result
function process(obj) {
 if (!cache.has(obj)) {
 let result = /* calculate the result for */ obj;

 cache.set(obj, result);
 }

 return cache.get(obj);
}

// 📁 main.js
let obj = {/* some object */};

let result1 = process(obj);
let result2 = process(obj);

// ...later, when the object is not needed any more:
obj = null;

// Can't get cache.size, as it's a WeakMap,
// but it's 0 or soon be 0
// When obj gets garbage collected, cached data will be removed as well

WeakSet

WeakSet se chová podobně:

  • Je analogický s Set , ale objekty můžeme přidávat pouze do WeakSet (ne primitiva).
  • Objekt existuje v sadě, zatímco je dosažitelný odjinud.
  • Jako Set , podporuje add , has a delete , ale ne size , keys() a žádné iterace.

Protože je „slabý“, slouží také jako další úložiště. Ale ne pro svévolné údaje, spíše pro fakta „ano/ne“. Členství v WeakSet může něco znamenat o objektu.

Například můžeme přidat uživatele do WeakSet abyste měli přehled o těch, kteří navštívili naše stránky:

let visitedSet = new WeakSet();

let john = { name: "John" };
let pete = { name: "Pete" };
let mary = { name: "Mary" };

visitedSet.add(john); // John visited us
visitedSet.add(pete); // Then Pete
visitedSet.add(john); // John again

// visitedSet has 2 users now

// check if John visited?
alert(visitedSet.has(john)); // true

// check if Mary visited?
alert(visitedSet.has(mary)); // false

john = null;

// visitedSet will be cleaned automatically

Nejvýznamnější omezení WeakMap a WeakSet je absence iterací a nemožnost získat veškerý aktuální obsah. To se může zdát nepohodlné, ale nebrání to WeakMap/WeakSet od vykonávání své hlavní práce – být „doplňkovým“ úložištěm dat pro objekty, které jsou uloženy/spravovány na jiném místě.

Shrnutí

WeakMap je Map -jako kolekce, která povoluje pouze objekty jako klíče a odstraňuje je spolu s přidruženou hodnotou, jakmile se stanou nepřístupnými jinými prostředky.

WeakSet je Set -jako kolekce, která ukládá pouze objekty a odstraňuje je, jakmile se stanou nedostupnými jinými prostředky.

Jejich hlavní výhodou je, že mají slabou vazbu na předměty, takže je může snadno odstranit sběrač odpadu.

To přichází za cenu toho, že nemáte podporu pro clear , size , keys , values

WeakMap a WeakSet se používají jako „sekundární“ datové struktury navíc k „primárnímu“ úložišti objektů. Jakmile je objekt odstraněn z primárního úložiště, pokud je nalezen pouze jako klíč WeakMap nebo v WeakSet , bude automaticky vyčištěn.