Javascript a oblast působnosti proměnných – díl druhý

Dnešní pokračování článku o oblastech působnosti proměnných v JavaScriptu se zaměří na poněkud náročnější problematiku. Tentokrát se věnujeme objektové části jazyka, probereme si pravidla, která platí pro objekty, třídy a metody, ukážeme si jak funguje zapouzdření a předvedeme si techniku „ultimátního zapouzdření“.

Seriál: Javascript a oblast působnosti proměnných (3 díly)

  1. Javascript a oblast působnosti proměnných – díl první 7.8.2009
  2. Javascript a oblast působnosti proměnných – díl druhý 12.8.2009
  3. Javascript a oblast působnosti proměnných – díl třetí 17.8.2009

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).

Autor je návrhář UI/UX, analytik, grafik, javascriptový vývojář a advocatus diaboli ex offo.

Věděli jste, že nám můžete zasílat zprávičky? (Jen pro přihlášené.)

Komentáře: 64

Přehled komentářů

ah01 Chybka v posldním příkladě
Michal Augustýn Re: Chybka v posldním příkladě
v6ak A co instanceof?
Mazarik Re: A co instanceof?
v6ak Re: A co instanceof?
Daniel Steigerwald Re: A co instanceof?
danaketh *THUMBS UP*
Aichi Re: Javascript a oblast působnosti proměnných - díl druhý
Michal Augustýn Re: Javascript a oblast působnosti proměnných - díl druhý
David Majda Formátování kódu
Timy Re: Formátování kódu
Martin Malý Re: Formátování kódu
David Majda Re: Formátování kódu
jos Re: Formátování kódu
vks Re: Formátování kódu
Honza77 Re: Formátování kódu
Jan Pejša Re: Formátování kódu
Daniel Steigerwald poznámky
Daniel Steigerwald Re: poznámky
Petr Staníček Re: poznámky
Aichi Re: poznámky
Petr Staníček Re: poznámky
Daniel Steigerwald js oop a vubec
Petr Staníček Re: js oop a vubec
jelc Re: js oop a vubec
v6ak Re: js oop a vubec
jelc Re: js oop a vubec
v6ak Re: js oop a vubec
jelc Re: js oop a vubec
v6ak Re: js oop a vubec
Michal Augustýn Re: js oop a vubec
v6ak Re: js oop a vubec
jelc Re: js oop a vubec
Michal Augustýn Re: js oop a vubec
karf Re: js oop a vubec
v6ak Re: js oop a vubec
Aichi Re: js oop a vubec
v6ak Re: js oop a vubec
Michal Augustýn Re: js oop a vubec
karf Re: js oop a vubec
Michal Augustýn Re: js oop a vubec
Michal Re: js oop a vubec
v6ak Re: js oop a vubec
Michal Re: js oop a vubec
v6ak Re: js oop a vubec
Michal Re: js oop a vubec
Aichi Re: poznámky
ah01 Re: poznámky
Aichi Re: poznámky
v6ak Re: poznámky
ah01 Re: poznámky
v6ak Re: poznámky
Aichi Re: poznámky
ah01 Re: poznámky
Aichi Re: poznámky
ah01 Re: poznámky
v6ak Re: poznámky
Jakub Vrána Velikost písmen
Petr Staníček Re: Velikost písmen
Michal Augustýn Re: Velikost písmen
Jakub Vrána Re: Velikost písmen
Jakub Vrána Re: Velikost písmen
Pixiho JS dobroty Re: Velikost písmen
lopata Moc rychle a zmateně
Zdroj: https://www.zdrojak.cz/?p=3061