Jak vÃme z kapitoly SbÄr odpadků, motor JavaScriptu si udržuje hodnotu v pamÄti, dokud je âdosažitelnáâ a může být použita.
PÅÃklad:
let jan = { jméno: "Jan" };
// k objektu může být pÅistupováno, jan je odkaz na nÄj
// pÅepÃÅ¡eme odkaz
jan = null;
// objekt bude odstranÄn z pamÄti
Obvykle jsou vlastnosti objektu nebo prvky pole Äi jiné datové struktury považovány za dosažitelné a udržovány v pamÄti, dokud je v pamÄti tato datová struktura.
NapÅÃklad uložÃme-li objekt do pole, pak dokud je toto pole živé, bude živý i tento objekt, i když na nÄj nebudou existovat žádné jiné odkazy.
TÅeba takto:
let jan = { jméno: "Jan" };
let pole = [ jan ];
jan = null; // pÅepÃÅ¡eme odkaz
// objekt, na který se dÅÃve odkazoval jan, je uložen uvnitÅ pole
// proto nebude odklizen sbÄraÄem odpadků
// můžeme k nÄmu pÅistoupit pomocà pole[0]
PodobnÄ když použijeme objekt jako klÃÄ v bÄžné mapÄ Map, pak dokud tato mapa bude existovat, bude existovat i tento objekt. Bude zabÃrat mÃsto v pamÄti a nebude moci být odstranÄn sbÄraÄem odpadků.
PÅÃklad:
let jan = { jméno: "Jan" };
let mapa = new Map();
mapa.set(jan, "...");
jan = null; // pÅepÃÅ¡eme odkaz
// jan je uložen uvnitŠmapy,
// můžeme k nÄmu pÅistoupit pomocà mapa.keys()
WeakMap (âslabá mapaâ) se v tomto ohledu zásadnÄ liÅ¡Ã. Nebránà odstraÅovánà svých klÃÄových objektů sbÄraÄem odpadků.
Na pÅÃkladech se podÃvejme, co to znamená.
WeakMap
PrvnÃm rozdÃlem mezi Map a WeakMap je, že klÃÄe musejà být objekty, ne primitivnà hodnoty:
let slabáMapa = new WeakMap();
let obj = {};
slabáMapa.set(obj, "ok"); // funguje správnÄ (klÃÄ je objekt)
// jako klÃÄ nemůžeme použÃt ÅetÄzec
slabáMapa.set("test", "Hop!"); // Chyba, protože "test" nenà objekt
Jestliže nynà použijeme objekt jako klÃÄ a nebudou na nÄj existovat žádné jiné odkazy â bude automaticky odstranÄn z pamÄti (a z mapy).
let jan = { jméno: "Jan" };
let slabáMapa = new WeakMap();
slabáMapa.set(jan, "...");
jan = null; // pÅepÃÅ¡eme odkaz
// jan se odstranà z pamÄti!
Srovnejte si to s výše uvedeným pÅÃkladem bÄžné mapy Map. Když nynà jan existuje jen jako klÃÄ WeakMap â bude automaticky smazán z mapy (a z pamÄti).
WeakMap nepodporuje iteraci a metody keys(), values(), entries(), neexistuje tedy žádný způsob, jak z nà zÃskat vÅ¡echny klÃÄe nebo hodnoty.
WeakMap má pouze následujÃcà metody:
slabáMapa.set(klÃÄ, hodnota)slabáMapa.get(klÃÄ)slabáMapa.delete(klÃÄ)slabáMapa.has(klÃÄ)
ProÄ takové omezenÃ? Je tomu tak z technických důvodů. Pokud objekt ztratil vÅ¡echny ostatnà odkazy (napÅ. jan v uvedeném kódu), má být automaticky odstranÄn sbÄraÄem odpadků. Technicky vÅ¡ak nenà pÅesnÄ specifikováno, kdy k odstranÄnà dojde.
O tom rozhoduje motor JavaScriptu. Ten se může rozhodnout provést úklid pamÄti okamžitÄ anebo s nÃm poÄkat a provést jej až pozdÄji, když se uskuteÄnà vÃce mazánÃ. Technicky tedy nenà znám aktuálnà poÄet prvků WeakMap. Motor je už mohl proÄistit nebo jeÅ¡tÄ ne, nebo to mohl udÄlat zatÃm jen ÄásteÄnÄ. Z tohoto důvodu nejsou podporovány metody, které pÅistupujà ke vÅ¡em klÃÄům a hodnotám.
K Äemu nynà takovou datovou strukturu potÅebujeme?
PÅÃpad použitÃ: dodateÄná data
Hlavnà oblastà použità WeakMap je úložiÅ¡tÄ dodateÄných dat.
Jestliže pracujeme s objektem, který âpatÅÃâ do jiného kódu, tÅeba i do knihovny tÅetà strany, a chtÄli bychom si uložit nÄjaká data s nÃm spojená, která by mÄla existovat, jen dokud je tento objekt živý â pak WeakMap je pÅesnÄ to, co potÅebujeme.
UložÃme data do WeakMap a onen objekt použijeme jako klÃÄ. Když bude objekt odklizen sbÄraÄem odpadků, data automaticky zmizà s nÃm.
slabáMapa.set(jan, "tajné dokumenty");
// jestliže jan zemÅe, tajné dokumenty budou automaticky zniÄeny
PodÃvejme se na pÅÃklad.
Máme napÅÃklad kód, který si udržuje poÄet návÅ¡tÄv jednotlivých uživatelů. Tato informace je uložena v mapÄ: objekt uživatele je klÃÄ a poÄet návÅ¡tÄv je hodnota. Když uživatel odejde (jeho objekt bude odklizen sbÄraÄem odpadků), nechceme již nadále mÃt poÄet jeho návÅ¡tÄv uložen.
Zde je pÅÃklad poÄÃtacà funkce s Map:
// ð poÄetNávÅ¡tÄv.js
let mapaPoÄetNávÅ¡tÄv = new Map(); // mapa: uživatel => poÄet návÅ¡tÄv
// zvýšà poÄet návÅ¡tÄv
function zapoÄÃtejUživatele(uživatel) {
let poÄet = mapaPoÄetNávÅ¡tÄv.get(uživatel) || 0;
mapaPoÄetNávÅ¡tÄv.set(uživatel, poÄet + 1);
}
A zde je dalšà Äást kódu, tÅeba dalšà soubor, který tuto funkci použÃvá:
// ð hlavnÃ.js
let jan = { jméno: "Jan" };
zapoÄÃtejUživatele(jan); // poÄet jeho návÅ¡tÄv
// pozdÄji nás jan opustÃ
jan = null;
Nynà by objekt jan mÄl být odklizen, ale zůstává v pamÄti, neboÅ¥ je to klÃÄ v mapÄ mapaPoÄetNávÅ¡tÄv.
Když tedy odstraÅujeme uživatele, musÃme mapu mapaPoÄetNávÅ¡tÄv proÄistit, jinak bude neustále narůstat v pamÄti. Ve složitých architekturách se takové proÄišťovánà může stát nepÅÃjemným úkolem.
Můžeme se tomu vyhnout, když použijeme WeakMap:
// ð poÄetNávÅ¡tÄv.js
let mapaPoÄetNávÅ¡tÄv = new WeakMap(); // slabá mapa: uživatel => poÄet návÅ¡tÄv
// zvýšà poÄet návÅ¡tÄv
function zapoÄÃtejUživatele(uživatel) {
let poÄet = mapaPoÄetNávÅ¡tÄv.get(uživatel) || 0;
mapaPoÄetNávÅ¡tÄv.set(uživatel, poÄet + 1);
}
Nynà mapu mapaPoÄetNávÅ¡tÄv Äistit nemusÃme. Jakmile se objekt jan stane nedosažitelným vÅ¡emi jinými způsoby než jako klÃÄ WeakMap, bude odstranÄn z pamÄti spolu s informacà uloženou pod tÃmto klÃÄem ve WeakMap.
PÅÃpad použitÃ: mezipamÄÅ¥
DalÅ¡Ãm bÄžným pÅÃkladem je mezipamÄÅ¥. Můžeme si ukládat do pamÄti (tato pamÄÅ¥ se nazývá âcacheâ nebo âmezipamÄÅ¥â) výsledky funkce, abychom je mohli znovu použÃt pÅi dalÅ¡Ãch volánÃch stejné funkce na témže objektu.
Abychom toho dosáhli, můžeme použÃt Map (neoptimálnà scénáÅ):
// ð mezipamÄÅ¥.js
let mezipamÄÅ¥ = new Map();
// vypoÄÃtá a zapamatuje si výsledek
function proces(obj) {
if (!mezipamÄÅ¥.has(obj)) {
let výsledek = /* výpoÄet výsledku pro */ obj;
mezipamÄÅ¥.set(obj, výsledek);
return výsledek;
}
return mezipamÄÅ¥.get(obj);
}
// Nynà použijeme proces() v jiném souboru:
// ð hlavnÃ.js
let obj = {/* ÅeknÄme, že máme nÄjaký objekt */};
let výsledek1 = proces(obj); // vypoÄteno
// ...pozdÄji z jiného mÃsta kódu...
let výsledek2 = proces(obj); // vezmeme z mezipamÄti výsledek, který si pamatujeme
// ...pozdÄji, když už tento objekt nebudeme potÅebovat:
obj = null;
alert(mezipamÄÅ¥.size); // 1 (Ouha! Objekt je stále v mezipamÄti a zabÃrá pamÄÅ¥!)
PÅi vÃce volánÃch proces(obj) nad stejným objektem funkce vypoÄÃtá výsledek jen poprvé a pak ho bude jednoduÅ¡e brát z mezipamÄÅ¥. Nevýhodou je, že když už objekt nebudeme potÅebovat, musÃme mezipamÄÅ¥ vyÄistit.
Když mÃsto Map použijeme WeakMap, tento problém zmizÃ. Výsledek v mezipamÄti bude z pamÄti odstranÄn automaticky poté, co bude objekt odklizen sbÄraÄem odpadků.
// ð mezipamÄÅ¥.js
let mezipamÄÅ¥ = new WeakMap();
// vypoÄÃtá a zapamatuje si výsledek
function proces(obj) {
if (!mezipamÄÅ¥.has(obj)) {
let výsledek = /* výpoÄet výsledku pro */ obj;
mezipamÄÅ¥.set(obj, výsledek);
return výsledek;
}
return mezipamÄÅ¥.get(obj);
}
// ð hlavnÃ.js
let obj = {/* nÄjaký objekt */};
let výsledek1 = proces(obj);
let výsledek2 = proces(obj);
// ...pozdÄji, když už tento objekt nebudeme potÅebovat:
obj = null;
// Nemůžeme zÃskat mezipamÄÅ¥.size, protože to je WeakMap,
// ale je nebo zanedlouho bude 0
// Když bude obj odklizen, budou odstranÄna i data z mezipamÄti
WeakSet
WeakSet (âslabá množinaâ) se chová obdobnÄ:
- Je analogická k
Set, ale doWeakSetmůžeme pÅidávat jedinÄ objekty (ne primitivy). - Objekt v této množinÄ existuje, dokud je dosažitelný odjinud.
- StejnÄ jako
Setpodporujeadd,hasadelete, ale nesize,keys()ani žádné iterace.
Protože je âslabáâ, může sloužit i jako úložiÅ¡tÄ dodateÄných dat. Ne vÅ¡ak pro libovolná data, ale jen pro skuteÄnost âano/neâ. Älenstvà ve WeakSet může o objektu nÄco znamenat.
NapÅÃklad můžeme do WeakSet pÅidávat uživatele, abychom si pamatovali ty, kteÅà navÅ¡tÃvili naÅ¡e stránky:
let množinaNávÅ¡tÄvnÃků = new WeakSet();
let jan = { jméno: "Jan" };
let petr = { jméno: "Petr" };
let marie = { jméno: "Marie" };
množinaNávÅ¡tÄvnÃků.add(jan); // navÅ¡tÃvil nás Jan
množinaNávÅ¡tÄvnÃků.add(petr); // pak Petr
množinaNávÅ¡tÄvnÃků.add(jan); // znovu Jan
// množinaNávÅ¡tÄvnÃků má nynà 2 uživatele
// ovÄÅÃme, zda nás navÅ¡tÃvil Jan
alert(množinaNávÅ¡tÄvnÃků.has(jan)); // true
// ovÄÅÃme, zda nás navÅ¡tÃvila Marie
alert(množinaNávÅ¡tÄvnÃků.has(marie)); // false
jan = null;
// množinaNávÅ¡tÄvnÃků bude automaticky proÄiÅ¡tÄna
NejvýznamnÄjÅ¡Ãm omezenÃm WeakMap a WeakSet je absence iteracà a nemožnost zÃskat celý jejich skuteÄný obsah. To se může zdát neÅ¡ikovné, ale nebránà to WeakMap/WeakSet v tom, aby odvádÄly svou hlavnà práci â být úložiÅ¡tÄm âdodateÄnýchâ dat pro objekty, které jsou uloženy nebo spravovány na jiném mÃstÄ.
ShrnutÃ
WeakMap je kolekce podobná Map, která dovoluje použÃvat jako klÃÄe jen objekty a odstraÅuje je i s pÅipojenou hodnotou, jakmile se stanou nedosažitelnými jiným způsobem.
WeakSet je kolekce podobná Set, která ukládá jen objekty a odstraÅuje je, jakmile se stanou nedosažitelnými jiným způsobem.
Jejich hlavnà výhodou je, že obsahujà slabé odkazy na objekty, takže ty mohou být snadno odklizeny sbÄraÄem odpadků.
Cenou za to je, že nejsou podporovány clear, size, keys, valuesâ¦
WeakMap a WeakSet se použÃvajà jako âsekundárnÃâ datové struktury navÃc k âprimárnÃmâ úložiÅ¡tÃm objektů. Když je objekt odstranÄn z primárnÃho úložiÅ¡tÄ, pak pokud se dá najÃt jen jako klÃÄ WeakMap nebo prvek WeakSet, bude automaticky odklizen.
KomentáÅe
<code>, pro nÄkolik Åádků je obalte znaÄkou<pre>, pro vÃce než 10 Åádků vložte odkaz na pÃskoviÅ¡tÄ (plnkr, jsbin, codepenâ¦)