Tento Älánek pokrývá velmi úzce zamÄÅené téma, s nÃmž se vÄtÅ¡ina vývojáÅů v praxi setká jen velmi vzácnÄ (a možná ani nevà o jeho existenci).
Pokud se JavaScript teprve zaÄÃnáte uÄit, doporuÄujeme vám tuto kapitolu pÅeskoÄit.
VzpomeÅme si na základnà koncept principu dosažitelnosti z kapitoly SbÄr odpadků a poznamenejme, že motor JavaScriptu zaruÄenÄ udržà v pamÄti hodnoty, které jsou dostupné nebo se použÃvajÃ.
NapÅÃklad:
// promÄnná uživatel obsahuje silný odkaz na objekt
let uživatel = { jméno: "Jan" };
// pÅepÃÅ¡eme hodnotu promÄnné uživatel
uživatel = null;
// odkaz je ztracen a objekt bude vymazán z pamÄti
Nebo podobný, ale troÅ¡ku složitÄjšà kód se dvÄma silnými odkazy:
// promÄnná uživatel obsahuje silný odkaz na objekt
let uživatel = { jméno: "Jan" };
// zkopÃrujeme silný odkaz na objekt do promÄnné správce
let správce = uživatel;
// pÅepÃÅ¡eme hodnotu promÄnné uživatel
uživatel = null;
// objekt je stále dosažitelný z promÄnné správce
Objekt { jméno: "Jan" } bude smazán z pamÄti teprve tehdy, až na nÄj nebudou existovat žádné silné odkazy (tedy až pÅepÃÅ¡eme i hodnotu promÄnné správce).
V JavaScriptu existuje koncept nazývaný WeakRef, který se v tomto pÅÃpadÄ chová ponÄkud odliÅ¡nÄ.
Silný odkaz â je odkaz na objekt nebo hodnotu, který zabraÅuje jejich smazánà sbÄraÄem odpadků. Udržuje tedy objekt nebo hodnotu v pamÄti, na nÞ ukazuje.
To znamená, že objekt nebo hodnota zůstane v pamÄti a nebude odklizen sbÄraÄem odpadků, dokud na nÄj budou existovat aktivnà silné odkazy.
BÄžné odkazy na objekty v JavaScriptu jsou silné odkazy. NapÅÃklad:
// promÄnná uživatel obsahuje silný odkaz na objekt
let uživatel = { jméno: "Jan" };
Slabý odkaz â je odkaz na objekt nebo hodnotu, který nebránà jejich smazánà sbÄraÄem odpadků. Objekt nebo hodnota může být smazán sbÄraÄem odpadků, jestliže vÅ¡echny zbývajÃcà odkazy na nÄj jsou slabé.
WeakRef
Než se do toho ponoÅÃme, stojà za zmÃnku, že korektnà použÃvánà struktur probÃraných v tomto Älánku vyžaduje velmi opatrné pÅemýšlenà a je lépe se jim vyhnout, pokud je to možné.
WeakRef â je objekt, který obsahuje slabý odkaz na jiný objekt, nazývaný cÃl nebo referent.
Zvláštnost WeakRef spoÄÃvá v tom, že nebránà sbÄraÄi odpadků v odstranÄnà svého referovaného objektu. Jinými slovy, objekt WeakRef neponechává objekt referent naživu.
VezmÄme nynà jako referovaný objekt promÄnnou uživatel a vytvoÅme na ni slabý odkaz z promÄnné správce.
Abychom vytvoÅili slabý odkaz, musÃme použÃt konstruktor tÅÃdy WeakRef a pÅedat mu cÃlový objekt (objekt, na který chceme slabý odkaz vytvoÅit).
V naÅ¡em pÅÃpadÄ to bude promÄnná uživatel:
// promÄnná uživatel obsahuje silný odkaz na objekt
let uživatel = { jméno: "Jan" };
// promÄnná správce obsahuje slabý odkaz na objekt
let správce = new WeakRef(uživatel);
NásledujÃcà diagram zobrazuje dva druhy odkazů: silný odkaz pomocà promÄnné uživatel a slabý odkaz pomocà promÄnné správce:
Pak v urÄitém okamžiku pÅestaneme použÃvat promÄnnou uživatel â bude pÅepsána, opustà rozsah platnosti atd., zatÃmco instanci WeakRef v promÄnné správce budeme udržovat:
// pÅepÃÅ¡eme hodnotu promÄnné uživatel
uživatel = null;
Slabý odkaz na objekt nestaÄà k jeho udrženà ânaživuâ. Když budou vÅ¡echny zbývajÃcà odkazy na referovaný objekt slabé, sbÄraÄ odpadků může tento objekt zniÄit a pamÄÅ¥, kterou objekt obsazoval, může být využita k nÄÄemu jinému.
Dokud vÅ¡ak nenà objekt skuteÄnÄ zniÄen, slabý odkaz jej může vracet, i když na nÄj už neexistujà žádné silné odkazy. Náš objekt se tedy stává nÄÄÃm jako âSchrödingerova koÄkaâ â nemůžeme s jistotou vÄdÄt, zda je âživýâ nebo âmrtvýâ:
Když v této chvÃli chceme zÃskat objekt z instance WeakRef, použijeme jejà metodu deref().
Metoda deref() vrátà referovaný objekt, na který WeakRef ukazuje, pokud je tento objekt stále v pamÄti. Jestliže byl objekt smazán sbÄraÄem odpadků, pak metoda deref() vrátà undefined:
let ref = správce.deref();
if (ref) {
// objekt je stále dostupný: můžeme s nÃm provádÄt jakékoli manipulace
} else {
// objekt byl odklizen sbÄraÄem odpadků
}
PÅÃpady použità WeakRef
WeakRef se obvykle použÃvá k vytváÅenà mezipamÄtà (cache) nebo asociativnÃch polÃ, do nichž se ukládajà objekty nároÄné na zdroje.
To nám umožÅuje vyhnout se tomu, aby tyto objekty nemohly být odklizeny sbÄraÄem odpadků jen proto, že jsou uloženy v mezipamÄti nebo asociativnÃm poli.
JednÃm z primárnÃch pÅÃkladů je situace, kdy máme vÄtšà množstvà objektů binárnÃch obrázků (reprezentovaných napÅÃklad jako ArrayBuffer nebo Blob) a chceme ke každému z nich pÅipojit název nebo cestu.
ExistujÃcà datové struktury nejsou pro tento úÄel pÅÃliÅ¡ vhodné:
- Použijeme-li k vytvoÅenà asociacà mezi názvy a obrázky nebo opaÄnÄ
Map, objekty obrázků budou stále zůstávat v pamÄti, neboÅ¥ se budou nacházet vMapjako klÃÄe nebo hodnoty. - Ani
WeakMapse pro tento úÄel nehodÃ, protože objekty pÅedstavované jako klÃÄeWeakMappoužÃvajà slabé odkazy a nejsou tedy chránÄny pÅed smazánÃm sbÄraÄem odpadků.
V této situaci vÅ¡ak potÅebujeme datovou strukturu, která by použÃvala slabé odkazy ve svých hodnotách.
K tomuto úÄelu můžeme použÃt kolekci Map, jejÞ hodnoty jsou instance WeakRef, které odkazujà na potÅebné velké objekty.
Důsledkem bude, že tyto velké a nepotÅebné objekty nebudeme udržovat v pamÄti déle, než bychom mÄli.
KromÄ toho to je způsob, jak zÃskat z mezipamÄti objekt obrázku, je-li stále dosažitelný. Pokud již byl odstranÄn sbÄraÄem odpadků, můžeme jej pÅegenerovat nebo stáhnout znovu.
TÃmto způsobem v nÄkterým situacÃch spotÅebujeme ménÄ pamÄti.
PÅÃklad ÄÃslo 1: použità WeakRef pro mezipamÄÅ¥
NásledujÃcà úryvek kódu demonstruje techniku použÃvánà WeakRef.
ZkrácenÄ ÅeÄeno, použijeme Map, jejÞ klÃÄe budou ÅetÄzce a jejÞ hodnoty budou objekty WeakRef.
Jestliže objekt ve WeakRef jeÅ¡tÄ nebyl odklizen sbÄraÄem odpadků, zÃskáme jej z mezipamÄti.
V opaÄném pÅÃpadÄ jej znovu stáhneme a uložÃme do mezipamÄti pro pÅÃpadné dalšà znovupoužitÃ:
function stáhniObrázek() {
// abstraktnà funkce pro stahovánà obrázků...
}
function najdiVMezipamÄti(stáhniObrázek) { // (1)
const pamÄÅ¥Obrázků = new Map(); // (2)
return (názevObrázku) => { // (3)
const obrázekVPamÄti = pamÄÅ¥Obrázků.get(názevObrázku); // (4)
if (obrázekVPamÄti?.deref()) { // (5)
return obrázekVPamÄti?.deref();
}
const novýObrázek = stáhniObrázek(názevObrázku); // (6)
pamÄÅ¥Obrázků.set(názevObrázku, new WeakRef(novýObrázek)); // (7)
return novýObrázek;
};
}
const vraÅ¥ObrázekVPamÄti = najdiVMezipamÄti(stáhniObrázek);
PodÃvejme se podrobnÄ, co se tady dÄje:
najdiVMezipamÄtiâ je funkce vyššÃho Åádu, která jako argument obdržà jinou funkci,stáhniObrázek. V tomto pÅÃkladu můžeme podrobný popis funkcestáhniObrázekvynechat, jelikož to může být jakákoli logika stahovánà obrázků.pamÄÅ¥Obrázkůâ je úložiÅ¡tÄ obrázků, do nÄhož se ukládajà výsledky funkcestáhniObrázekve formÄ ÅetÄzcových klÃÄů (název obrázku) a objektů tÅÃdyWeakRefjako hodnot.- VrátÃme anonymnà funkci, jejÃmž argumentem je název obrázku. Tento argument bude použit jako klÃÄ pro uložený obrázek.
- SnažÃme se zÃskat uložený výsledek z úložiÅ¡tÄ podle zadaného klÃÄe (název obrázku).
- Jestliže úložiÅ¡tÄ obsahuje pro zadaný klÃÄ nÄjakou hodnotu a objekt
WeakRefnebyl odklizen sbÄraÄem odpadků, vrátÃme uložený výsledek. - Jestliže pro požadovaný klÃÄ neexistuje v úložiÅ¡ti žádný záznam nebo metoda
deref()vrátÃundefined(což znamená, že objektWeakRefbyl odklizen sbÄraÄem odpadků), funkcestáhniObrázekstáhne obrázek znovu. - Stažený obrázek uložÃme do úložiÅ¡tÄ jako objekt
WeakRef.
Nynà máme kolekci Map, jejÞ klÃÄe jsou ÅetÄzce s názvy obrázků a jejÞ hodnoty jsou objekty WeakRef obsahujÃcà samotné obrázky.
Tato technika nám pomůže vyhnout se zabÃránà velkého množstvà pamÄti pro objekty nároÄné na zdroje, které již nikdo nepoužÃvá. RovnÄž Å¡etÅà pamÄÅ¥ a Äas v pÅÃpadÄ opakovaného použità uložených objektů.
Následuje vizuálnà reprezentace toho, jak tento kód vypadá:
Tato implementace vÅ¡ak má své nevýhody: po Äase bude Map plná ÅetÄzcových klÃÄů, které ukazujà na WeakRef, jejichž referovaný objekt již byl odstranÄn sbÄraÄem:
JednÃm způsobem, jak tento problém vyÅeÅ¡it, je pravidelnÄ procházet úložiÅ¡tÄ a odstraÅovat âmrtvéâ záznamy. DalÅ¡Ãm způsobem je použÃt finalizátory, které prozkoumáme pozdÄji.
PÅÃklad ÄÃslo 2: použità WeakRef ke sledovánà DOM objektů
DalÅ¡Ãm pÅÃpadem použità WeakRef je sledovánà DOM objektů.
PÅedstavme si scénáÅ, v nÄmž kód nebo knihovna tÅetà strany pracuje s elementy na našà stránce tak dlouho, dokud existujà v DOMu. Může to být napÅÃklad externà utilita pro monitorovánà a oznamovánà stavu systému (bÄžnÄ nazývaná âloggerâ â program, který posÃlá informaÄnà zprávy nazývané âlogyâ).
Interaktivnà pÅÃklad:
const tlaÄÃtkoZaÄÃtPosÃlat = document.querySelector('.start-messages'); // (1)
const tlaÄÃtkoZavÅÃtOkno = document.querySelector('.window__button'); // (2)
const odkazNaElementOkna = new WeakRef(document.querySelector(".window__body")); // (3)
tlaÄÃtkoZaÄÃtPosÃlat.addEventListener('click', () => { // (4)
startMessages(odkazNaElementOkna);
tlaÄÃtkoZaÄÃtPosÃlat.disabled = true;
});
tlaÄÃtkoZavÅÃtOkno.addEventListener('click', () => document.querySelector(".window__body").remove()); // (5)
const zaÄniPosÃlatZprávy = (element) => {
const idÄasovaÄe = setInterval(() => { // (6)
if (element.deref()) { // (7)
const zátÄž = document.createElement("p");
zátÄž.textContent = `Zpráva: Stav systému OK: ${new Date().toLocaleTimeString()}`;
element.deref().append(zátÄž);
} else { // (8)
alert("Element byl odstranÄn."); // (9)
clearInterval(idÄasovaÄe);
}
}, 1000);
};.app {
display: flex;
flex-direction: column;
gap: 16px;
}
.start-messages {
width: fit-content;
}
.window {
width: 100%;
border: 2px solid #464154;
overflow: hidden;
}
.window__header {
position: sticky;
padding: 8px;
display: flex;
justify-content: space-between;
align-items: center;
background-color: #736e7e;
}
.window__title {
margin: 0;
font-size: 24px;
font-weight: 700;
color: white;
letter-spacing: 1px;
}
.window__button {
padding: 4px;
background: #4f495c;
outline: none;
border: 2px solid #464154;
color: white;
font-size: 16px;
cursor: pointer;
}
.window__body {
height: 250px;
padding: 16px;
overflow: scroll;
background-color: #736e7e33;
}<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="index.css">
<title>WeakRef DOM Logger</title>
</head>
<body>
<div class="app">
<button class="start-messages">ZaÄÃt posÃlat zprávy</button>
<div class="window">
<div class="window__header">
<p class="window__title">Zprávy:</p>
<button class="window__button">ZavÅÃt</button>
</div>
<div class="window__body">
Žádné zprávy.
</div>
</div>
</div>
<script type="module" src="index.js"></script>
</body>
</html>Když kliknete na tlaÄÃtko âZaÄÃt posÃlat zprávyâ, zaÄnou se objevovat zprávy (logy) v tzv. âoknÄ zobrazovánà zprávâ (element tÅÃdy .window__body).
Jakmile je vÅ¡ak tento element z DOMu smazán, logger by mÄl pÅestat zprávy zasÃlat. Abyste odstranÄnà tohoto elementu reprodukovali, staÄà kliknout na tlaÄÃtko âZavÅÃtâ v pravém hornÃm rohu.
Abychom si nekomplikovali práci a neposÃlali oznámenà kódu tÅetà strany pokaždé, když je a když nenà náš DOM element k dispozici, postaÄà na nÄj vytvoÅit slabý odkaz pomocà WeakRef.
Když bude element odstranÄn z DOMu, logger si toho vÅ¡imne a pÅestane zprávy posÃlat.
Nynà se blÞe podÃvejme na zdrojový kód (záložka index.js):
-
ZÃskáme DOM element tlaÄÃtka âZaÄÃt posÃlat zprávyâ.
-
ZÃskáme DOM element tlaÄÃtka âZavÅÃtâ.
-
ZÃskáme DOM element okna zobrazenà logů pomocà konstruktoru
new WeakRef(). Pak bude promÄnnáodkazNaElementOknaobsahovat slabý odkaz na tento DOM element. -
PÅidáme k tlaÄÃtku âZaÄÃt posÃlat zprávyâ posluchaÄe událostÃ, který bude zajišťovat rozbÄhnutà loggeru po kliknutà na tlaÄÃtko.
-
PÅidáme k tlaÄÃtku âZavÅÃtâ posluchaÄe událostÃ, který bude zajišťovat zavÅenà okna s logy po kliknutà na tlaÄÃtko.
-
PomocÃ
setIntervalzaÄneme každou sekundu zobrazovat zprávu. -
Jestliže je DOM element okna zobrazujÃcÃho zprávy stále dostupný a udržovaný v pamÄti, vytvoÅÃme a poÅ¡leme novou zprávu.
-
Jestliže metoda
deref()vrátÃundefined, znamená to, že DOM element byl odstranÄn z pamÄti. V tom pÅÃpadÄ logger pÅestane zobrazovat zprávy a zrušà ÄasovaÄ. -
Tento
alertbude volán poté, co bude DOM element okna zobrazovánà zpráv odstranÄn z pamÄti (tj. po kliknutà na tlaÄÃtko âZavÅÃtâ). VÅ¡imnÄte si, že k odstranÄnà z pamÄti nemusà dojÃt okamžitÄ. Závisà to výhradnÄ na vnitÅnÃch mechanismech sbÄraÄe odpadků.Tento proces nemůžeme ovládat pÅÃmo z kódu. PÅesto vÅ¡ak máme možnost vynutit si spuÅ¡tÄnà sbÄru odpadků z prohlÞeÄe.
NapÅÃklad v Google Chrome to provedeme tak, že otevÅeme vývojáÅské nástroje (Ctrl + Shift + J ve Windows/Linuxu nebo Option + â + J v macOS), pÅejdeme na záložku âVýkonâ (âPerformanceâ) a klikneme na ikonu odpadkového koÅ¡e â âUvolnÄnà pamÄtiâ (âCollect garbageâ):
Tuto funkcionalitu podporuje vÄtÅ¡ina modernÃch prohlÞeÄů. Po provedenà tÄchto akcà se okamžitÄ spustÃalert.
FinalizationRegistry
Nynà nastal Äas pohovoÅit o finalizátorech. Než budeme pokraÄovat, ujasnÃme si terminologii:
Ãklidový callback (finalizátor) â je funkce, která je spuÅ¡tÄna, když je objekt registrovaný ve FinalizationRegistry odstranÄn z pamÄti sbÄraÄem odpadků.
JejÃm úÄelem je poskytnout možnost provést dalšà operace vztahujÃcà se k objektu poté, co byl definitivnÄ odstranÄn z pamÄti.
Registr (nebo FinalizationRegistry) â je speciálnà objekt v JavaScriptu, který spravuje registraci a deregistraci objektů a jejich úklidových callbacků.
Tento mechanismus umožÅuje registrovat objekt ke sledovánà a pÅipojit k nÄmu úklidový callback. V zásadÄ je to struktura, která ukládá informace o registrovaných objektech a jejich úklidových callbaccÃch a pak, když jsou objekty odstranÄny z pamÄti, tyto callbacky automaticky vyvolává.
Abychom vytvoÅili instanci tÅÃdy FinalizationRegistry, musÃme volat jejà konstruktor, který obsahuje jediný argument â úklidový callback (finalizátor).
Syntaxe:
function úklidovýCallback(uchovávanáHodnota) {
// kód úklidového callbacku
}
const registr = new FinalizationRegistry(úklidovýCallback);
Zde:
úklidovýCallbackâ úklidový callback, který bude automaticky zavolán, až bude registrovaný objekt odstranÄn z pamÄti.uchovávanáHodnotaâ hodnota, která je pÅedána úklidovému callbacku jako argument. PokuduchovávanáHodnotaje objekt, registr si na nÄj uchovává silný odkaz.registrâ instance tÅÃdyFinalizationRegistry.
Metody tÅÃdy FinalizationRegistry:
-
register(cÃl, uchovávanáHodnota [, registraÄnÃZnámka])â použÃvá se k registraci objektů v registru.cÃlâ objekt registrovaný pro sledovánÃ. Pokud jecÃlodstranÄn sbÄraÄem odpadků, bude zavolán úklidový callback s argumentemuchovávanáHodnota.Nepovinná
registraÄnÃZnámkaâ známka pro zruÅ¡enà registrace. Můžeme ji pÅedat, abychom mohli registraci objektu zruÅ¡it jeÅ¡tÄ dÅÃve, než jej sbÄraÄ odpadků odstranÃ. Zpravidla se jakoregistraÄnÃZnámkapoužÃvá objektcÃl, což je standardnà praktika. -
unregister(registraÄnÃZnámka)â metodaunregisterse použÃvá ke zruÅ¡enà registrace objektu v registru. Obsahuje jeden argument âregistraÄnÃZnámka(známka pro zruÅ¡enà registrace, která byla zadána pÅi registraci objektu).
PÅejdÄme nynà k jednoduchému pÅÃkladu. Využijme již známý objekt uživatel a vytvoÅme instanci tÅÃdy FinalizationRegistry:
let uživatel = { jméno: "Jan" };
const registr = new FinalizationRegistry((uchovávanáHodnota) => {
console.log(`${uchovávanáHodnota} byl odstranÄn sbÄraÄem odpadků.`);
});
Pak objekt, pro který požadujeme úklidový callback, zaregistrujeme volánÃm metody register:
registr.register(uživatel, uživatel.jméno);
Registr si na registrovaný objekt neuchovává silný odkaz, protože by tÃm ztratil smysl. Kdyby si jej uchovával, objekt by nikdy nebyl sbÄraÄem odpadků odklizen.
Jestliže objekt bude odklizen sbÄraÄem odpadků, může být nÄkdy v budoucnu zavolán náš úklidový callback, kterému se pÅedá uchovávanáHodnota:
// Když je objekt uživatel odklizen sbÄraÄem odpadků, vypÃÅ¡e se na konzoli následujÃcà zpráva:
"Jan byl odstranÄn sbÄraÄem odpadků."
Existujà vÅ¡ak situace, v nichž se i v implementacÃch využÃvajÃcÃch úklidový callback může stát, že úklidový callback nebude nikdy zavolán.
NapÅÃklad:
- Když bÄh programu zcela skonÄà (napÅ. pÅi zavÅenà záložky v prohlÞeÄi).
- Když se samotná instance
FinalizationRegistrystane nedosažitelnou z JavaScriptového kódu. Jestliže objekt, který vytvoÅil instanci tÅÃdyFinalizationRegistry, opustà rozsah platnosti nebo bude smazán, úklidové callbacky registrované v tomto registru nemusejà být vyvolány.
Ukládánà do mezipamÄti pomocà FinalizationRegistry
Když se vrátÃme k naÅ¡emu pÅÃkladu slabé mezipamÄti, můžeme si vÅ¡imnout následujÃcÃho:
- I když byly hodnoty zabalené do
WeakRefodklizeny sbÄraÄem odpadků, stále je tady problém s âúnikem pamÄtiâ v podobÄ zbývajÃcÃch klÃÄů, jejichž hodnoty sbÄraÄ odpadků odstranil.
Následuje vylepÅ¡ený pÅÃklad mezipamÄti s využitÃm FinalizationRegistry:
function stáhniObrázek() {
// abstraktnà funkce pro naÄÃtánà obrázků...
}
function najdiVMezipamÄti(stáhniObrázek) {
const pamÄÅ¥Obrázků = new Map();
const registr = new FinalizationRegistry((názevObrázku) => { // (1)
const obrázekVPamÄti = pamÄÅ¥Obrázků.get(názevObrázku);
if (obrázekVPamÄti && !obrázekVPamÄti.deref()) pamÄÅ¥Obrázků.delete(názevObrázku);
});
return (názevObrázku) => {
const obrázekVPamÄti = pamÄÅ¥Obrázků.get(názevObrázku);
if (obrázekVPamÄti?.deref()) {
return obrázekVPamÄti?.deref();
}
const novýObrázek = stáhniObrázek(názevObrázku);
pamÄÅ¥Obrázků.set(názevObrázku, new WeakRef(novýObrázek));
registr.register(novýObrázek, názevObrázku); // (2)
return novýObrázek;
};
}
const vraÅ¥ObrázekVPamÄti = najdiVMezipamÄti(stáhniObrázek);
-
Abychom zajistili úklid âmrtvýchâ záznamů v mezipamÄti, když jsou pÅipojené objekty ve
WeakRefodklizeny sbÄraÄem odpadků, vytvoÅÃme úklidový registrFinalizationRegistry.Důležitým bodem zde je, že v úklidovém callbacku bychom mÄli ovÄÅovat, zda záznam byl odstranÄn sbÄraÄem odpadků a nebyl znovu pÅidán, abychom nesmazali âživýâ záznam.
-
Když je stažena a umÃstÄna do mezipamÄti nová hodnota (obrázek), zaregistrujeme ji v registru finalizátorů, abychom mohli sledovat objekt
WeakRef.
Tato implementace obsahuje pouze aktuálnà neboli âživéâ dvojice klÃÄ/hodnota. V tomto pÅÃpadÄ je každý objekt WeakRef zaregistrován ve FinalizationRegistry a poté, co jsou objekty odstranÄny sbÄraÄem odpadků, úklidový callback smaže vÅ¡echny hodnoty undefined.
Na obrázku je vizuálnà reprezentace vylepšeného kódu:
KlÃÄovým aspektem vylepÅ¡ené implementace je, že finalizátory umožÅujà vytváÅenà paralelnÃch procesů mezi âhlavnÃmâ programem a úklidovými callbacky. V kontextu JavaScriptu je âhlavnÃmâ programem náš JavaScriptový kód, který je spuÅ¡tÄn a bÄžà v našà aplikaci nebo webové stránce.
Proto může mezi okamžikem, kdy je objekt oznaÄen ke smazánà sbÄraÄem odpadků, a skuteÄným spuÅ¡tÄnÃm úklidového callbacku nastat urÄitá Äasová prodleva. Je důležité pochopit, že bÄhem této prodlevy může hlavnà program provést v objektu jakékoli zmÄny nebo jej dokonce vrátit zpÄt do pamÄti.
Z tohoto důvodu musÃme v úklidovém callbacku ovÄÅovat, zda hlavnà program nepÅidal záznam zpÄt do mezipamÄti, abychom se vyhnuli mazánà âživýchâ záznamů. ObdobnÄ když v mezipamÄti hledáme klÃÄ, je možné, že jeho hodnota byla vymazána sbÄraÄem odpadků, ale úklidový callback jeÅ¡tÄ nebyl spuÅ¡tÄn.
Takové situace vyžadujà pÅi práci s FinalizationRegistry zvláštnà pozornost.
Použità WeakRef a FinalizationRegistry v praxi
PÅejdÄme od teorie k praxi. PÅedstavte si scénáŠze skuteÄného života, kdy si uživatel synchronizuje své fotografie na mobilnÃm zaÅÃzenà s nÄjakou cloudovou službou (napÅ. iCloud nebo Fotky Google) a chce si je prohlÞet z jiných zaÅÃzenÃ. Takové služby nabÃzejà kromÄ základnà functionality prohlÞenà fotografià Åadu dalÅ¡Ãch funkcÃ, napÅÃklad:
- Editaci fotografià a videoefektů.
- VytváÅenà âvzpomÃnekâ a alb.
- Montáž videa ze série fotografiÃ.
- â¦a mnoho dalÅ¡Ãho.
Jako pÅÃklad zde uvedeme pomÄrnÄ primitivnà implementaci takové služby. HlavnÃm smyslem je ukázat možný scénáŠspoleÄného použità WeakRef a FinalizationRegistry ve skuteÄném životÄ.
Bude to vypadat následovnÄ:
Na levé stranÄ je cloudová knihovna fotografià (jsou zobrazeny jako náhledy). Můžeme si zvolit obrázky, jaké chceme, a kliknutÃm na tlaÄÃtko âVytvoÅit kolážâ na pravé stranÄ stránky z nich vytvoÅit koláž. Výslednou koláž si pak můžeme stáhnout jako obrázek.
Abychom zvýšili rychlost naÄÃtánà stránky, bude rozumné stahovat a zobrazovat náhledy fotografià v komprimované kvalitÄ. Když vÅ¡ak ze zvolených fotografià budeme vytváÅet koláž, stáhneme a použijeme je v plné kvalitÄ.
NÞe vidÃme, že vnitÅnà velikost náhledů je 240x240 pixelů. Tuto velikost jsme zvolili, abychom zvýšili rychlost nahrávánÃ. NavÃc v režimu prohlÞenà nepotÅebujeme fotografie v plné velikosti.
PÅedpokládejme, že potÅebujeme vytvoÅit koláž ÄtyÅ fotografiÃ: vybereme si je a pak klikneme na tlaÄÃtko âVytvoÅit kolážâ. V tuto chvÃli funkce
najdiVMezipamÄti, kterou už známe, ovÄÅÃ, zda je požadovaný obrázek v mezipamÄti. Pokud ne, stáhne jej z cloudu a uložà jej do mezipamÄti pro dalšà použitÃ. To se bude odehrávat pro každý zvolený obrázek:
Když budete sledovat výstup na konzoli, uvidÃte, které z fotografià byly staženy z cloudu â ty jsou oznaÄeny jako STAŽENÃ_OBRÃZEK (FETCHED_IMAGE). Protože to je prvnà pokus o vytvoÅenà koláže, znamená to, že v této chvÃli je âslabá mezipamÄÅ¥â jeÅ¡tÄ prázdná, a tak vÅ¡echny fotografie budou staženy z cloudu a uloženy do nÃ.
SpoleÄnÄ s procesem stahovánà obrázků je zde vÅ¡ak také proces ÄiÅ¡tÄnà pamÄti sbÄraÄem odpadků. To znamená, že objekt uložený v mezipamÄti, na který se odkazujeme slabým odkazem, je smazán sbÄraÄem odpadků. A náš finalizátor se úspÄÅ¡nÄ spustà a tÃm smaže klÃÄ, pod nÃmž byl obrázek uložen v mezipamÄti. Oznamuje nám to SMAZANÃ_OBRÃZEK (DELETED_IMAGE):
Pak zjistÃme, že se nám výsledná koláž nelÃbÃ, a rozhodneme se jeden obrázek zmÄnit a vytvoÅit novou. To udÄláme jednoduÅ¡e tak, že smažeme nechtÄný obrázek, vybereme jiný a opÄt klikneme na tlaÄÃtko âVytvoÅit kolážâ:
Tentokrát vÅ¡ak vÅ¡echny obrázky nebyly staženy ze sÃtÄ, ale jeden z nich byl pÅevzat ze slabé mezipamÄti: o tom nám ÅÃká zpráva OBRÃZEK_Z_PAMÄTI (CACHED_IMAGE). To znamená, že v okamžiku vytvoÅenà koláže sbÄraÄ odpadků náš obrázek jeÅ¡tÄ nesmazal a my jsme ho jen vytáhli z mezipamÄti, ÄÃmž jsme snÞili poÄet sÃÅ¥ových požadavků a urychlili celkovou dobu procesu vytváÅenà obrázků:
âPohrajme siâ jeÅ¡tÄ dál. Znovu nahraÄme jeden z obrázků jiným a vytvoÅme novou koláž:
Výsledek je tentokrát jeÅ¡tÄ působivÄjÅ¡Ã. Ze ÄtyÅ zvolených obrázků byly tÅi vytaženy ze slabé mezipamÄti a pouze jeden musel být stažen ze sÃtÄ. SÃÅ¥ová zátÄž byla snÞena zhruba o 75%. Þasné, ne?
Je samozÅejmÄ důležité mÃt na pamÄti, že takové chovánà nenà zaruÄeno a závisà na specifické implementaci a operacÃch sbÄraÄe odpadků.
PÅi tom okamžitÄ vyvstává zcela logická otázka: proÄ se spoléháme na sbÄraÄ odpadků a nepoužijeme mÃsto toho obyÄejnou mezipamÄÅ¥, v nÞ bychom mohli záznamy spravovat sami? Je to pravda, v naprosté vÄtÅ¡inÄ pÅÃpadů nenà potÅeba WeakRef a FinalizationRegistry použÃvat.
Zde jsme jednoduÅ¡e pÅedvedli alternativnà implementaci podobné functionality použitÃm netriviálnÃho pÅÃstupu se zajÃmavými vlastnostmi jazyka. Stále se vÅ¡ak na tento pÅÃklad nemůžeme spolehnout, potÅebujeme-li stabilnà a pÅedvÃdatelný výsledek.
Tento pÅÃklad si můžete otevÅÃt na pÃskoviÅ¡ti.
ShrnutÃ
WeakRef â byla navržena pro vytváÅenà slabých odkazů na objekty a umožÅuje jejich smazánà z pamÄti sbÄraÄem odpadků, pokud na nÄ už neexistujà žádné silné odkazy. To se vyplatà pro adresovánà rozsáhlého využità pamÄti a optimalizaci využità systémových zdrojů v aplikacÃch.
FinalizationRegistry â je nástroj pro registraci callbacků, které budou spuÅ¡tÄny pÅi smazánà objektů, na které již neexistujà žádné silné odkazy. To nám umožÅuje uvolnit zdroje spojené s tÄmito objekty nebo provést jiné nezbytné operace, než bude objekt z pamÄti 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â¦)