V minulé části trojdílného článku jsme si zopakovali pravidla, která v JavaScriptu platí pro obory působnosti proměnných a funkcí. To byla ta triviálnější část problematiky platnosti proměnných v Javascriptu. S pokročilejšími technikami programování se zesložiťuje i tato otázka.
Třídy a metody
Pokud začneme v Javascriptu pracovat s objekty, přestaneme si stačit s jednoduchým pohledem na jednotlivé úrovně kódu a musíme zohlednit další rozměr, a to čas. Přesněji řečeno, musíme začít důsledněji rozlišovat, kdy jaký objekt vzniká a v jakém je v daném okamžiku stavu – kdy se vlastně tvoří příslušné obory působnosti.
function Trida() { var x = 1; alert(x); this.krok = function() { x++; alert(x); } } var Obj = new Trida(); Obj.krok(); Obj.krok(); var Obj2 = new Trida(); Obj2.krok();
Zde nejprve definujeme třídu Trida
. To je stejné jako definice funkce, proto se během načítání ještě nic nestane. Následně vytváříme objekt Obj
jako instanci této třídy. To opět odpovídá volání funkce a provede se její tělo. Deklaruje se její lokální proměnná x
a vypíše se její hodnota 1. Poté se volá metoda krok
, která hodnotu x
zvýší o jedna a zobrazí 2. Následně se zavolá znovu, hodnota x
je stále 2, zvýší opět o jedna a zobrazí se 3. Poté vytvoříme druhý objekt Obj2
jako (jinou) instanci téže třídy a vše proběhne znovu. Vytvoří se „klon“ se svou vlastní lokální proměnou x
, té se přiřadí 1 a zobrazí, pak se zavolá metoda krok
, zvýší hodnotu na 2 a zobrazí. Tento kód tedy bude zobrazovat postupně hodnoty 1, 2, 3, 1, 2.
Vlastnosti objektů
V předchozím příkladu byla proměnná x
lokální proměnnou třídy Trida
(potažmo jejích instancí), a tedy byla dostupná pouze zevnitř jejího kódu, zvenčí jakoby neexistovala. Udělejme ale jednu změnu, nahraďme lokální proměnnou var x
vlastností objektu this.x
:
function Trida() { this.x = 1; alert(this.x); this.krok = function() { this.x++; alert(this.x); } } var Obj = new Trida(); Obj.krok(); Obj.krok(); alert(Obj.x);
Namísto deklarování lokální proměnné x
používá třída „vlastnost“ x
. Ta se deklaruje automaticky při prvním přiřazení. Chování tohoto kódu bude zcela shodné – ovšem s jedním podstatným rozdílem: toto x
není již lokální, ale je dostupná i zvenčí jako vlastnost příslušné instance, zde tedy Obj.x
, které je dostupné stejně, jako je dostupný objekt Obj
.
Platí přitom, že klíčové slovo this
má – narozdíl od var
– platnost pro celý mateřský objekt. Pokud deklarujeme nějakou vlastnost uvnitř metody, jedná se vždy o vlastnost „globální“ pro celý objekt, nikoli jen pro tuto metodu.
function Trida() { this.krok = function() { var x = 1; this.x = 1; } this.krok2 = function() { alert(this.x); alert(x); } } var Obj = new Trida(); Obj.krok(); Obj.krok2();
Zde se na nejvyšší úrovni třídy Trida
žádné x
nedefinuje. Vytvoříme instanci Obj
a zavoláme její metodu krok
. Ta deklaruje svou lokální proměnnou x
a také prvním přiřazením vytvoří vlastnost this.x
, která už ale není její lokální, ale je vlastností celého objektu typu Trida
. když následně zavoláme druhou metodu krok2
, vidíme, že hodnotu this.x
zná a zobrazí, zatímco proměnná x
je pro ni neznámá a ohlásí chybu Javascriptu (zde si povšimněme, že v kódu tříd je deklarování proměnných povinné).
Zapouzdření
Tento rozdíl mezi lokálními proměnnými a vlastnostmi objektů je velmi důležitý zejména při psaní různých knihoven a sdílených skriptů. Cokoli deklarujeme v objektech pomocí var
, bude dostupné jen lokálně, nebude zvenčí přístupné. Cokoli deklarujeme jako vlastnost pomocí this
, bude veřejné a zvenčí dostupné.
Proměnné, které chceme mít dostupné zvenčí, tedy zavedeme jako „vlastnost“ daného objektu, zatímco privátní proměnné deklarujeme jako lokální a zvenčí se k nim nebude možné dostat.
function MojeKnihovnaClass() { // veřejné: this.Verze = '1.0'; this.Vysledek; // privátní var Mezivysledek, ZdrojovaData; // metody this.metoda1 = function() { // ... } } var MojeKnihovna = new MojeKnihovnaClass();
Pozornější čtenář si ihned povšimne jednoho háčku: máme rozlišené veřejné a privátní proměnné, ale metody našeho objektu jsou veřejné všechny. Což není vždy (dokonce málokdy) žádoucí. Ale i to lze snadno vyřešit. Úplně stejně, jako rozlišujeme veřejné vlastnosti a privátní lokální proměnné, můžeme rozlišovat veřejné metody a privátní lokální funkce.
function MojeKnihovnaClass() { // veřejné: this.X; this.Y; // privátní var X1, X2; // veřejné metody this.metoda1 = function() { // ... spocitej(this.X,this.Y); } // privátní funkce function spocitej(x,y) { /* ... */ } } var MojeKnihovna = new MojeKnihovnaClass();
Funkce deklarované jako metody pomocí this
budou veřejně dostupné, zatímco privátní funkce budou dostupné pouze lokálně a nebudou dostupné zvenčí. Při svém volání jsou ale přitom stále v oboru polatnosti celého objektu a jsou v nich dostupné nejen všechny jeho lokální proměnné a ostatní privátní funkce, ale i veřejné vlastnosti a metody.
Ultimátní zapouzdření
Při tvorbě knihoven a jiných sdílených skriptů ale často potřebujeme omezit úplně všechny interakce s naším objektem na minimum a snažíme se „nepustit“ ven ani dovnitř nic, co bychom výslovně nechtěli. Řešením je použití instance anonymní třídy – kdy sice stejně jako v předchozím případě vytvoříme třídu, ale nikam ji neukládáme: ihned vytvoříme její instanci, která bude mít veřejně dostupné pouze vlastnosti a metody, které explicitně vrátíme hodnotou return
.
Následující příklad ukazuje takové řešení v praxi. Interakci naší knihovny s okolím tvoří jediná proměnná, kterou stačí pojmenovat vhodným unikátním jménem, aby se zamezilo kolizím s cizími „jmennými prostory“ a vše ostatní už zůstává zapouzdřeno v těle anonymní funkce. Tu ihned voláme (povšimněme si ()
ihned za tělem funkce) a z vnějšku tak dostupné v naší proměnné zůstane pouze to, co tato funkce vrátí jako svou návratovou hodnotu. V tomto případě tedy objekt s vlastností Verze
a metodami vystup
a zverejniVerzi
. Původní anonymní funkce pro všechny přestane existovat, pouze objekt, který byl jejím výstupem, si odkaz na ni ponechá a její obsah mu zůstane přístupný. Pouze jemu a už nikomu jinému.
var MojeKnihovna = function() { // vše privátní this.InterniVerze = '1.0.12356b'; var spojka = ' + '; function spoj(x,y) { return x + spojka + y; } function dejInterniVerzi() { return this.InterniVerze; } // veřejné metody return { Verze : '1.0', vystup : function(x,y) { return spoj(x,y); }, zverejniVerzi : function() { return dejInterniVerzi(); } } }(); alert( MojeKnihovna.vystup('a','b') ); alert( MojeKnihovna.Verze ); alert( MojeKnihovna.InterniVerze ); alert( MojeKnihovna.zverejniVerzi() );
Pokud zavoláme dostupnou metodu vystup
, nejprve se zavolá lokální funkce spoj
a předá jí naše parametry. Tato lokální funkce už má přístup k veškerému internímu privátnímu kódu, tedy i lokální proměnné spojka
, spojí řetězce, vrátí je zpět a vypíše se a + b
. V druhém kroku se bez problémů vypíše hodnota vlastnosti Verze
, protože ta je veřejná (byla vrácena původní funkcí). V dalším kroku se hodnota vlastnosti InterniVerze
nevypíše, protože tato vlastnost jako veřejná předána nebyla a pro veřejnost je tedy neznámá a nedefinovaná (vypíše se undefined
). Když bychom zkusili zavolat MojeKnihovna.dejInterniVerzi()
, vyvoláme chybu Javascriptu, protože tato metoda vůbec není deklarována ( dejInterniVerzi
je lokální, privátní funkce). Ovšem v posledním kroku, kdy se používá veřejná metoda zverejniVerzi
, se zavolá lokální funkce dejInterniVerzi
a ta privátní vlastnost InterniVerze
do rozhraní předá.
Tento postup je tedy jakýmsi „svatým Grálem“ javascriptových knihoven, protože stoprocentně zapouzdřuje veškerý náš kód a okolí zpřístupňuje pouze to, co výslovně ke zpřístupnění určíme. Mnoho moderních javascriptových knihoven a frameworků (včetně těch nejpoužívanejších, např. jQuery) je zapoudřeno právě tímto způsobem.
V další, už poslední části seriálu, přijdou na řadu uzávěry (closures).
Přehled komentářů