Přejít k navigační liště

Zdroják » JavaScript » Javascript a oblast působnosti proměnných – díl třetí

Javascript a oblast působnosti proměnných – díl třetí

Články JavaScript, Různé

Poslední díl článku Petra Staníčka o oblasti působnosti proměnných se věnuje uzávěrům (closures) v JavaScriptu a použití klíčového slova this.

Uzávěry

Uzávěr (angl. closure) je pro mnoho lidí nejsložitějším partem javascriptového programování a patrně sbírkou největších mystérií, která v souvislosti s Javascriptem kolují. Taky jsem kolem toho velmi dlouho tápal a nebyl s to tento problém pevně uchopit za pačesy. Nedávno jsem se tím snad prokousal a k svému překvapení objevil nečekaně snadné a pochopitelné pravidlo a úhel pohledu, z nějž je na uzávěry nejlépe koukat.

Nejprve oč jde. Pokud v Javascriptu vytvoříte globální anonymní funkci, její chování je poměrně jasné – při svém spuštění se chová jako globální a má k dispozici globální proměnné platné v okamžiku svého volání. Problém ale nastává, když takovou anonymní funkci vytvoříme uvnitř jiné funkce. Je vytvořena v určitém lokálním kontextu, ale volána může být kdykoli jindy, většinou v situaci, kdy onen lokální kontext už dávno neexistuje.

function zpracuj(elm) {
   var x = 1;
   elm.onclick = function(){
      alert(x);
      }
   }
var elm = document.getElementById('tlacitko1');
zpracuj(elm);

Zavoláme funkci zpracuj, ta si vytvoří lokální proměnnou x a následně prvku elm přiřadí pro zpracování události onclick novou anonymní funkci, která používá tuto proměnnou x. Funkce zpracuj poté skončí a její lokální proměnné (včetně toho x) zmizí. Ovšem když uživatel klikne na tlačítko, událost onclick se vyvolá a spustí se ona anonymní funkce, která jí byla přiřazena a která používá proměnnou x z kontextu, který již neexistuje. Co se má v tuto chvíli stát?

Řešením jsou právě zmíněné uzávěry. Tento princip programovacího jazyka říká, že v těchto případech se má vytvořit jakási záloha kontextu, který byl v době vytvoření oné anonymní funkce platný, a ten bude mít tato funkce k dispozici v době svého volání.

V uvedeném případě tedy událost onclick dostane přiřazen nejen kód funkce, který se má při jejím vyvolání zpracovat, ale také lokální kontext funkce zpracuj, který měla v době, kdy tento ovladač vytvářela. V našem případě tedy po kliknutí na tlačítko bude mít ovladač k dispozici kopii proměnné x a vypíše hodnotu 1.

Ovšem to podstatné, co je jádrem k pochopení celé problematiky uzávěrů je fakt, že tím kontextem se nemyslí stav a hodnoty všech proměnných v okamžiku vytvoření té funkce. Pokud si to člověk promyslí do důsledků, zjistí, že s tím by bylo mrzení až hanba. Zjednodušeně řečeno anonymní funkce v uzávěru dostane požadovaný kontext až ve stavu, v jakém je ve chvíli volání této funkce. A pakliže tento kontext už neexistuje a ta „mateřská“ funkce, která uzávěr vytvořila, již dávno skončila, dostane uzávěr zálohu jejího kontextu v posledním známém stavu. Jinými slovy: uzávěr dostane všechny proměnné, které byly k dispozici při skončení běhu funkce, v níž vznikl.

function zpracuj(elm) {
   elm.onclick = function(){ alert(x); }
   var x = 20;
   x++;
   }
var elm = document.getElementById('tlacitko1');
zpracuj(elm);

Zde opět ve funkci zpracuj přiřadíme prvku elm ovladač události onclick. Ten má zobrazit hodnotu x. V daném kontextu je to proměnná lokální a z hlediska principu uzávěru vůbec nesejde na tom, jakou měla hodnotu v okamžiku vytvoření toho ovladače, ba ani že v tom okamžiku dokonce ještě nebyla ani deklarována. Ovladač (ona anonymní funkce) pro svůj běh dostane v rámci uzávěru kontext takový, jaký byl až po dokončení funkce zpracuj  – tedy proměnná x bude deklarována a bude mít hodnotu 21. Což je také hodnota, kterou tento ovladač po kliknutí na tlačítko zobrazí.

A to je v zásadě celý trik s uzávěry. Pokud kdekoli uvnitř nějaké funkce definujeme nějaký ovladač či jinou anonymní funkci, stačí jen myslet na to, že tato funkce nebude mít k dipozici proměnné tak, jak vypadají právě teď, když tu funkci tvoříme, ale tak, jak budou vypadat na konci, až ta vnější funkce skončí – a máme vyhráno. Zkuste si malý test:

<button id="tlacitko1">test 1</button>
<button id="tlacitko2">test 2</button>
<button id="tlacitko3">test 3</button>

<script type="text/javascript">
function zpracuj() {
   var i, elm;
   for (i=1;i<=3;i++) {
      elm = document.getElementById('tlacitko'+i);
      elm.onclick = function(){ alert(i); }
      }
   }
zpracuj();
</script>

Co se zobrazí po kliknutí na jednotlivá tlačítka? Kdo na první pohled pozná, že všechna tři zobrazí shodně hodnotu 4, má už uzávěry v malíčku. Kdo hádal něco jiného, nechť si krok po kroku projde činnost funkce zpracuj, poznamená si někam hodnotu proměnné i po jejím skončení a pak si zkusí zahrát na ten ovladač, co by asi tak zobrazil.

Nesmí ovšem dojít k mýlce. Uzávěr nedostává nějakou „mrtvou“ kopii kontextu, jde o kontext zcela plnohodnotný. Pokud „mateřská“ funkce dosud běží, pracuje uzávěr přímo v jejím kontextu, pokud už skončila, pracuje v původním kontextu, který zůstal zakonzervován. Pokud v rámci jedné funkce vytvoříme více uzávěrů, nevytvoří se nějaká kopie kontextu pro každý z nich. Budou sdílet týž společný kontext původní funkce a mohou v něm navzájem interagovat.

function zpracuj() {
   var elm;
   elm = document.getElementById('tlacitko');
   elm.onmouseover = function(){ counter++ }
   elm.onmouseout = function(){ this.innerHTML = counter }
   var counter = 100;
   }
zpracuj();

Ve funkci zpracuj vytváříme dvě anonymní funkce: ovladače pro onmouseover a onmouseout. Oba budou při svém spuštění (po vyvolání příslušné události) používat zakonzervovaný kontext funkce zpracuj, a to pro oba společný. Budou sdílet stejný uzávěr. Již víme, že vůbec nezáleží na tom, že proměnná counter v době definování ovladačů ještě neexistovala – hlavní je, že existuje v době jejich volání. Obě funkce dostanou sdílený kontext, v němž je proměnná counter deklarována a má hodnotu 100. Pokud našem tlačítku vyvoláme událost onmouseover, zvýší se hodnota counter o jedničku, po vyvolání události onmouseout se tato hodnota zapíše jako obsah tlačítka; přičemž kontext obou uzávěrů bude trvat dál. Když tedy budeme myší přejíždět nad tlačítkem, bude se v něm postupně zobrazovat hodnoty 101, 102, 103 atd., vždy o jedna vyšší při každém přejezdu.

Tohle & Tamto

Posledním kamenem úrazu bývá použití klíčového slova this, a to nejčastěji právě v uzávěrech. Může to sice někdy být složité a náročné na přemýšlení, ale stačí jen myslet na to, co toto magické slovo vlastně vyjadřuje. A neříká nic jiného, než že odpovídá na otázku, kdo, resp. kde právě jsem?

Uvnitř definice tříd a objektů odkazuje na objekt samotný a není s tím obvykle žádné trápení. Ovšem narazíme v situaci, kdy zde vytváříme nějaký nový (anonymní) objekt nebo novou (anonymní) funkci a dojde na uplatnění uzávěru. Neboť význam klíčového slova this se zkoumá a převádí na jemu odpovídající objekt až v okamžiku volání dotyčné funkce – a snadno se stane, že v tu chvíli je odpovědí na onu otázku „kdo jsem / kde jsem“ něco úplně jiného než v době vytváření.

var XYZ = {};
XYZ.test = function(param) {
   this.X = param;
   var init = function() {
      if (this.X==undefined) alert('chyba');
      }
   init();
   };
XYZ.test(1);

V našem objektu XYZ z nějakého (jistě dobrého) důvodu přiřazujeme init anonymní funkci, která zde má otestovat hodnotu, kterou jsme si přiřadili do vlastnosti X našeho objektu. Ovšem když to v této podobě vyzkoušíme, zjistíme, že to nebude fungovat a chyba se vypíše s jakýmkoli parametrem. Je to proto, že this vyjadřuje aktuální informaci „kdo jsem / kde jsem“, což v případě běhu oné anonymní funkce už neznamená objekt XYZ. V okamžiku svého volání se už nenachází v jeho kontextu, je „vytržena“ do kontextu globálního a this v jejím případě označuje globální objekt. Pokud bychom si místo alert(„chyba“) nechali vypsat hodnotu this, zjistíme, že jí je globální objekt  window.

Řešení je v těchto případech prosté. Stačí si hodnotu this poznamenat do nějaké lokální proměnné (obvyklé je that), která se měnit nebude a všechny anonymní funkce i cizí objekty ji budou mít k dipozici, a to i uvnitř případného uzávěru.

var XYZ = {};
XYZ.test = function(param) {
   this.X = param;
   var that = this;
   var init = function() {
      if (that.X==undefined) alert('chyba');
      }
   init();
   };
XYZ.test(1);

A ejhle, již vše funguje, jak má. Anonymní funkce dostane v rámci uzávěru proměnné z kontextu funkce XYZ.text, tedy i hodnotu that odpovídající objektu XYZ a skutečnost, že hodnota this se změnila na referenci na úplně jiný objekt, už nás vůbec nemusí trápit.

Stejné je to v případě zpracování ovladačů událostí nebo odkazování funkcí v  setTimeout.

var XYZ = {};
XYZ.test = function(ID) {
   var that = this;
   this.elm = document.getElementById(ID);
   this.elm.onchange = function() {
      that.aktualizuj(this.value);
      that.pockej(3000);
      }
   this.aktualizuj = function(hodnota) { /* ... */ }
   this.pockej = function(ms){
      setTimeout(that.dokonci,ms);
      }
   this.dokonci = function() { alert('hotovo') }
   };
XYZ.test('selectbox');

Metodě XYZ.test předáme ID nějakého HTML selectu. Ta jeho události onchange přiřadí jako ovladač anonymní funkci, která se přes připravenou lokální proměnnou that může odkazovat na další metody objektu XYZ. Při volání toho ovladače odpovídá hodnota this prvku, na kterém událost vznikla, čehož využije pro zjištění jeho hodnoty, kterou zpracuje a zavolá metodu pockej. Ta opět díky proměnné that, kterou mají anonymní funkce v uzávěru dostupnou,zavolá se zpožděním metodu dokonci. V praxi to bude vypadat tak, že změní-li se hodnota v našem prvku selectbox, zavolá se metoda aktualizuj a za 3 sekundy se zavolá metoda dokonci, která zobrazí hlášku „hotovo“.

Závěr

Vida, ona to nakonec až taková věda není. Proměnné i funkce platí v té části kódu, v níž byly vytvořeny – pokud to bylo na nejvyšší úrovni, jsou dostupné globálně; pokud to bylo uvnitř funkce, jsou dostupné jen uvnitř ní a zvenčí nikoli; vlastnosti a metody objektů jsou veřejně dostupné, jejich lokální proměnné a funkce nikoli. A pakliže uvnitř nějaké funkce vytvoříme anonymní funkci, bude při svém volání pracovat v uzávěru, který zůstal zachován z běhu funkce, která jej vytvořila. Jestli jste v některých otázkách platnosti proměnných v Javascriptu neměli úplně jasno, snad nyní tápete o něco méně. Mně osobně tohle shrnutí docela pomohlo.

Pozn.: Na tento článek jsem se chystal už dva týdny, shodou okolností právě v den, kdy jsem se do něj pustil, vyšlo podobné téma na Smashing Magazine. Nemůžu předstírat, že jsem tento článkem neviděl, nicméně jsem jej nikterak vědomě nekopíroval. Náhody si nevybírají.

Komentáře

Subscribe
Upozornit na
guest
41 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
Aichi

Pokud píšu o uzávěrech (já vím, psal jste, že tomu nerozumíte, proto o tom píšete), tak je velice jednoduché skončit u that = this. Pak se ale nedivme, že lidi co k tomu příjdou se ptaj, proč je někde this a někde that.

Mnohem lepší by bylo zmínit možnost zakonzervovat si this pomocí konstrukce apply.

fos4

Byl jste rychlejsi, zrovna jsem chtel napsat :

var XYZ = {};
XYZ.test = function(param) {
   this.X = param;
   var init = function() {
      if (this.X==undefined) alert('chyba');
      else alert('je to ok ');
   }
   init.call(this); // init.apply(this);
};
XYZ.test(1);
_

Mnohem lepší by bylo zmínit možnost zakonzervovat si this pomocí konstrukce apply.
Čo myslíte pod tým zakonzervovaním?
Bez toho, aby bolo to pôvodné this do niečoho uložené to nepôjde.

pracj3am

tak bez anonymní funkce to bude také fungovat

var XYZ = {};
XYZ.test = function(param) {
   this.X = param;
   function init() {
      if (this.X==undefined) alert('chyba');
      else alert('je to ok ');
   }
   init();
};
XYZ.test(1);
Aichi

A spustil jste si to?

pracj3am

Zkusil a zjistil, že je jedno jestli je funkce anonymní nebo ne.

Mám tam ale jedno mlhavé místo, kód níže vrací:

chyba
chyba
je to ok
je to ok

A tomu druhému řádku příliš nerozumím. (Možná v seriálu chybí lepší vysvětlení operátoru new.)

var XYZ = {};
XYZ.test = function(param) {
   this.X = param;
   var init = function() {
      if (this.X==undefined) alert('chyba');
      else alert('je to ok ');
   }
   init();
};
var test = XYZ.test;

XYZ.test(1);
A = new XYZ.test(1);
test(1);
B = new test(1);
Aichi

Opravdu zkusil? a opravdu ten vas priklad dava stejnou hodnotu jako ten nahore? Ne nedava! takze to jedno neni. Jake mlhave misto make krom toho new? Jaky druhy radek?

Nicmene pro vasi informaci, ty vase 4 posledni radky: – Prvni je stejny jako vyse a dokazuje, ze nemate pravdu. – Druhym volate funkci test jako konstruktor a tedy v A je objekt s jedinou vlastnosti a to X, funkce init se k nemu nevztahuje a tudiz nezna this jako tento objekt – Treti je volani pouze funkce test, pak this = globalni objekt (window) a cirou nahodou je this v init take globalni objekt, pak to vypise OK – Ctvrty je to same jako Druhe, nicmene vam predchozi volani nadefinovalo window.X, tudiz to projde, kdyby byl zakomentovan treti radek, neprojde to taky

Co presne nechapete na operatoru new?

pracj3am

Opravdu jsem si to zkusil, anonymni nebo neanonymni funkce – vysledek stejny:

var XYZ = {};
XYZ.test = function(param) {
   this.X = param;
   var init = function() {
      if (this.X==undefined) alert('chyba');
      else alert('je to ok ');
   }
   function initb() {
      if (this.X==undefined) alert('chyba');
      else alert('je to ok ');
   }
   init();
   initb();
};

Jsem zmaten kvůli tomu, že autor psal, že lokálně definované funkce uvnitř konstruktoru slouží jako privátní metody a že jsou v nich přístupné všechny lokální i veřejné proměnné. Což tedy, jak se ukazuje, není pravda.

Aichi

Problém vašeho zmatení je nejspíš v tom, te v tomto případu žádný konstruktor není.

ah01

this nemůžete brát jako „normální“ proměnnou. V lokálně definované funkci jsou přístupné lokální proměnné díky uzávěře (closure). this se ale uzávěra netýká, o tom je právě druhá část tohoto článku (část Tohle & Tamto).

cHLeB@

A zrovna o tedle moznosti (apply / call) se pise v clanku na ktery pixi odkazuje pod svym clankem .. :) … docela sranda :)

Jakub Vrána

Zmatek s velikostí písmen bohužel pokračuje. Jeho důsledkem je teď to, že se článek odkazuje na neexistující objekt Window (ve skutečnosti je to objekt  window).

v6ak

Budu chytat za slovo. Zkusil jsem do asresního řádku prohlížeče (FF řady 3.5) napsat obojí: javascript:aler­t(Window) javascript:aler­t(window)

Hele, oboje háže „[object Window]“.

No dobře, oboje je něco jinýho. javascript:aler­t(Window.docu­ment) vs. javascript:aler­t(window.docu­ment) to koneckonců dokazuje. Window je AFAIK „třída“ (prototyp) pro window.

Toto ukazuje, jak je důležité na takovou konvenci dbát!

Daniel Steigerwald

Upřesním. Když se v kódu objeví ‚Window‘, je jasné, že autor neví která bije. Internet Explorer (6/7) totiž na rozdíl od ostatních prohlížečů neodkazuje na konstrukční funkci (constructor), ale na historické dědictví, známé jako [Interface prototype object]. Internet Explorer podporuje plnohodnotný prototype/con­structor pouze pro objekty: Array, Boolean, Date, Error, Function, Number, Object, RegExp a String. [Interface prototype object] je něco jako polo-prototype, instancím lze přidat vlastnosti, avšak instance samotné constructor nemají. Navíc je tento interface podporován pouze pro několik BOM typů: window, option, image (myslím, že to jsou všechny) Žádný smysluplný kód nikdy nikde ‚Window‘ nepoužívá, snad krom nějakých prehistorických copy&past mousehover příkladů.

Jakub Vrána

Ano, důležitost konvence se ukáže třeba v případě, kdy alert(Window) zkusíš v IE nebo když v kterémkoliv prohlížeči napíšeš třeba alert(new regexp). Firefox v alertu píše název konstrukční funkce, ze které je objekt odvozen, např. u document to je HTMLDocument.

radik

Nevim jak ostatnim, ale ja kdyz si chtel vytisknout clanek z Firefoxu, tak zmizely vsechny zdrojaky, zustaly z nich jen cisla radek ;)

radik

Tak dela to nejen v FF, ale i v IE, O a Ch.

Martin Malý

Máte pravdu. To není příjemné, předám to vývojovému oddělení k prozkoumání. Do té doby si zkuste před tiskem vypnout JS. Za potíže se omlouváme.

Jakub Vrána

Uzávěr sice skutečně vidí proměnné deklarované ve funkci až po jeho definici, ale je lepší se tomu vyhnout. Uzávěr se o proměnné totiž dozví až když k její deklaraci interpret skutečně dojde. Do té doby ale může uzávěr někdo zavolat (ať už sama funkce nedopatřením nebo třeba asynchronně při obsluze nějaké události). V tom případě by vznikla při zápisu do proměnné uzávěrem globální proměnná. Lepší je proto deklarovat všechny proměnné vždy na začátku funkce.

Chování lze ověřit na tomto kódu:

function f() {
    var g = function () {
        x = 2;
    }
    g();
    var x = 1;
}
f();
console.log(x);

Další užitečné konvence uvádí Dougles Crockford.

_

V vašom kóde vidí interpret x vo funkcii g hneď, no má hodnotu undefined.
Takže ku vytvoreniu globálnej premennej x v žiadnom prípade nepríde.

v6ak

To je kvalitní nesmysl! Jak ho má vidět?

_

Neviem na koho reagujete, použil som terminológiu J. Vrány o „videní“ premennej.
Treba vyskúšať tento kód:

function f() {
  var g = function () {
    alert(x); // undefined
    x = 2;
  }
  alert(x); // undefined
  g();
  alert(x); // 2
  var x = 1;
  alert(x); // 1
}
f();
alert(x);  // vyvolá chybu, globálna premenná x neexistuje
v6ak

Omlouvám se, máte pravdu. Koukám, že v tomto se JS chová zvláštně a že mě stále může i zde překvapit. To, co mě dostalo, je možné demonstrovat snadno: 

function f() {
  e = 5;
  var e;
};
f();
alert(window.e); // Pokud ve funkci f vznikla vlastnost globálního objektu e místo lokální proměnné e,
// vypíše pětku.
// Pokud funkce f touto chybou netrpí, vypíše undefined.

Když zakomentuju řádek „var e;“, tak se změní chování. A <b>vůbec nevadí, že proměnnou vařím až po jejím používání!</b>

Viděli jste už někdo někde jinde takovouto zvláštní směs statického a dynamického chování?

_

To vysvetlenie nie je až tak zložité, nasledujúce dve definície funkcie sú ekvivalentné – ak sú miesto toho komentára tie isté príkazy:

function f(){
  var x;
  /*
  príkazy
  */
  x = 1;
}
function f(){
  /*
  príkazy
  */
  var x = 1;
}

Podobne sa chovajú aj funkcie, je ich možné volať už pred ich definovaním, teda pokiaľ sú definované tak, ako vyššie, nie literálom. Kód je spracovávaný na dvakrát a takéto veci sú spracované pri tej prvej analýze.

v6ak

Já jsem to pochopil, ale zdá se mi to divné…

v6ak

Shrnul bych to takto: * při obsluze události to nastat jen tak nemůže, události jsou řazeny do fronty ke zpracování v jednom vlákně. Výjimku by ale mohlo tvořit třeba yield a volání zevnitř funkce není nemožné. * Globální proměnná takto vzniknout nemůže. * Rozhodně je přehlednější „varovat“ proměnné před použitím než až po použití. Už kvůli zvyku programátorů z jiných jazyků. * Pozdější „varování“ proměnných může také vést k jejich inicializaci po určité úpravě.

Ve výsledku bych považoval konvenci „nejdřív ‚varuj‘“ za něco, co skoro nic nestojí, ale může se hodit a je to slušné. Podobně jako odsazování kódu.

Michal Augustýn

Možná by ještě stálo za zmínku, že closure v JavaScriptu vzniká vždy na úrovni funkce. To proto, že v jiných jazycích s funkcionálními rysy (např. C#) může closure vzniknout na úrovni bloku.

_

Tým svojim osvieteným pohľadom len mätiete ostatných.
Tá premenná x po skončení funkcie zpracuj vôbec nezmizne,
ale práve zostane uchovaná v tej funkcii zadanej do toho onclicku.
Toto vysvetlenie je oveľa jednoduchšie, než tie „zálohy kontextu“ so špeciálnym správaním.
Treba vyskúšať tento kód a bude to oveľa jasnejšie, že žiadne zálohy kontextu sa nerobia a pracuje sa vo všetkých funkciách s tou istou premennou x:

var ukazx1, ukazx2, nastavx1, nastavx2;
function f(){
  var x;
  function u1(){alert(x);}
  function u2(){alert(x);}
  function n1(t){x = t;}
  function n2(t){x = t;}
  ukazx1 = u1;
  ukazx2 = u2;
  nastavx1 = n1;
  nastavx2 = n2;
}
f();
nastavx1(1);
ukazx1(); // 1
ukazx2(); // 1
nastavx2(2);
ukazx1(); // 2
ukazx2(); // 2
_

Pred tým komentárom som to neprečítal celé, takže tá kritika nie je úplne oprávnená,
no to o tých „zálohách kontextu“ platí.

Michal Augustýn

IMHO to o „zálohách kontextu“ platí. Ve Vašem příkladě se také jedná o „zálohu kontextu“ – je zde ale pouze jedna, protože funkce f je volána jen jednou – to Vás asi zmátlo.
Prostě proměnná x existuje vždy v nějakém kontextu a pokud někdo ten kontext nějak referencuje, tak ho GC nepožere, takže pořád existuje. V uvedených případech je kontext referencován přes proměnnou x, která je referencovaná vnořenou funkcí (resp. funkcemi), jejíž reference je přiřazena do globální proměnné.

_

Kód mi jasný, nič ma nezmiatlo. Ono trochu záleží, čo je pod tým zmiznutím premennej a kontexte myslené. Ako definujete ten kontext? Lepšie by bolo povedať, že na tú premennú existujú viaceré odkazy a netreba potom rozmýšľať o „kontexte“, platnom, keď funkcia skončí, ako v článku. Ten „uzáver“ by nejakým spôsobom mohol fungovať, aj keby funkcia neskončila. Teda uvažovať tak, že je referencia na tú premennú x a nie na nejaký ťažko definovateľný kontext.

t42

mozna spis nez „zaloha kontextu“ by slo pouzit „reference na kontext“, alespon takto se closures popisuji v anglicky psane literature a tak mi to prijde presnejsi, protoze „zaloha“ IMHO implikuje spis klon, ktery nema vztah ke svemu originalu, pritom ale o ten vztah jde predevsim (bez nej by nebylo treba delal triky s prirazovanim iteracni promenne do lokalni promenne nebo s prirazovanim this do that, prave proto, aby vznikla closure)

ale jinak velmi kvalitni clanek

v6ak

„reference na kontext“ To je celkem vystihující. Běh funkce má referenci na svůj kontext, kam se ukládají lokální proměnné. Je-li vytvořena lokální funkce, pak samotná lokální funkce dostává referenci na kontext běhu vnější funkce. V případě zavolání této funkce vznikne kontext, který se smíchaný s kontextem přiřazeným k funkci, takže vidím i proměnné z vnějších funkcí. V souvislosti s referencemi bývá zmiňován garbage collector. Zde by fungovalo i počítání referencí, bylo-li by správně napsáno. Ten vysvětluje ty „zálohy“ elegantněji: pokud funkce doběhne, pak běhu této funkce zanikne reference. Pokud tu ale je reference na její kontext, pak prostě nebude uklizen. To mi zní čistěji než „záloha kontextu“. Ve výsledku to ale zvenku vyjde nastejno. Implementačními detaily se zde nezabýváme.

_

Celý ten pojem „kontextu“ mi príde nadbytočný.
Čo je chybné na úvahe, že premenná x v prvom kóde článku existuje ďalej aj po skončení funkcie zpracuj?
V tej funkcii predanej do onclick nie je vytváraná žiadna ďalšia premenná, tak sa mi zdá logické a najjednoduchšie uvažovať o tom, že premenná x existuje aj po skončení funkcie zpracuj.

_

V článku je že: … proměnná counter v době definování ovladačů ještě neexistovala … (4. kód)
No v skutočnosti tá premenná vtedy už existovala, len jej hodnota bola vtedy undefined.

Michal Augustýn

Pokud se máme bavit o JavaScriptu obecně, pak by v souvislosti s closures stálo za zmínku klíčové slovíčko let, které umožní vytvořit closure uvnitř funkce. Dostupné je od verze 1.7.

_

To je vec, ktorá by nefungovala vo veľkej časti súčasných prehliadačov so zapnutým funkčným JavaScriptom.
Vytvorenie „uzáverov“ je možné aj iným spôsobom, no je to zlé riešenie, pretože je taký kód ťažko optimalizovateľný, napríklad:

with({n:1}) var f = function(){return n++;};
Michal Augustýn

Proto jsem psal „Pokud se máme bavit o JavaScriptu obecně“.
A ano, bez použití letu je IMHO jediným způsobem, jak vytvořit closure, vytvoření další funkce.

_

Nie je to jediný spôsob, dá sa použiť with, no je to zlé riešenie:

function f(){
var i=1;
alert(i); // 1
with({i:2})alert(i); // 2
alert(i); // 1
}
f();
Dan

Děkuji moc za tuto sérii článků, hodně mi pomohly objasnit kontexty v JS. Jen tak dál!

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.