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

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

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

Články JavaScript, Různé

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í“.

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

Komentáře

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

V posledním příkladě vám patrně unikl pozornosti řádek

this.InterniVerze = '1.0.12356b';

ono this v tomto případě totiž ukazuje na globální objekt window. Takže z InterniVerze se stala rázem veřejná proměnná a já můžu psát:

alert(InterniVerze); // -> 1.0.12356b

// nebo dokonce
InterniVerze = 10;
alert( MojeKnihovna.zverejniVerzi() );

To je dost velká díra v zapouzdření.

PS: Jinak, se mi série článků líbí a těším se na třetí díl.

Michal Augustýn

Samozřejmě máte pravdu. Možná byl autor ovlivněn podobným přístupem k ultimátnímu zapouzdření, který je také občas k vidění (ale většinou to bývá zbytečný overkill):

var MojeKnihovna = new function() {
  // vše privátní (ale jen vůči vnějšku)
  var _this = this;
  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();
    }
  }
};

Btw. globálním objektem nemusí být window, ale záleží na prostředí, ve kterém JavaScript běží ;-)

v6ak

K tomu doporučenému postupu bych dodal, že typy jdou do kytek a instanceof nemůže fungovat. V případě výjimek to je určitě problém: http://www.devshed.com/…-Handling/4/

Mazarik

V Javascripte niekedy typeof rozumne fungoval? vid. typeof(new String()) Musi sa to riesit napr. hladanim cez (new String()).toSou­rce() pripadne vo vlastnych objektoch v toString.

v6ak

Nemluvím o typeof, ale o instanceof.

Daniel Steigerwald

typeof funguje jak má, string není problém typeof str == ‚string‘. Pokud chcete detekovat boxované primitivní proměnné, zavolejte na nich valueOf. typeof String(‚foo‘)­.valueOf() == ‚string‘

danaketh

Pěkný seriál. Alespoň si doplním znalosti JavaScriptu.

Aichi

Problém s instanceof tu již zmíněn byl, další co bych vytknul je používání slova třída. Javascript třídy nemá a je to matoucí.

Javascript je prototypový jazyk a má prototypové objekty. Je jasné, že tu budete vykřikovat o tom jak je to text pro zelenáče, ale učit je něco co není je dost krátkozraké.

Dále mě zaráží nezmínka o definování vlastností a funkcí přes prototype a všechny omezení a výhody oproti zmiňovanému způsobu (min. z hlediska dědění a vytváření dalších kopií funkcí při vytvoření nové instance).

Michal Augustýn

Absence prototypování/dě­dění mě také mrzí, ale když se nad tím člověk zamyslí, tak by to vydalo na další článek, možná dokonce seriál, takže se není čemu divit…

David Majda

Dotaz na redakci – bylo by možné u ukázek kódu používat standardní styl formátování, při kterém je ukončovací složená závorka odsazená o úroveň méně než kód uvnitř bloku, který uzavírá?

Chápu, že autor má svůj specifický styl formátování kódu, ale troufnu si tvrdit, že drtivá většina čtenářů Zdrojáku je zvyklá na styl standardní a použitý styl odsazování tak kód velmi znepřehledňuje. Pro mě osobně je kód s posunutou ukončovací složenou závorkou téměř nečitelný.

Timy

Přimlouvám se taktéž, na ten poslední kus kódu jsem dlouho hleděl jako sůva z nudlí, než jsem se zorientoval.

Martin Malý

Odsazení uzavíracích závorek je věcí konvence, nikoli „standardů“. Tudíž redakce nevydává žádná závazná typografická pravidla, týkající se odsazování či odřádkování, a pokud jsou zdrojové kódy čitelné, tak je jejich formátování prohlášeno za dostatečné. Tyto zdrojové kódy jsou čitelné a odsazování je v nich konzistentní – byť nemusí odpovídat stylu (konvenci), kterou používá ten či onen čtenář.

David Majda

Odsazení uzavíracích závorek je věcí konvence, nikoli „standardů“.

Pokud nějakou konvenci používají prakticky všichni, je slovo standard na místě. Je jedno, jestli je oficiální/neo­ficiální, psaný/nepsaný apod.

Tyto zdrojové kódy jsou čitelné

Bohužel v tomto bodě se názorem lišíme.

jos

Pokud nějakou konvenci používají prakticky všichni

ehm, na to existuje nějaká statisika ?

a i kdyby jo, 80% všech statistik je vycucaných z prstu

vks

Osobně si myslím, že kod je v pohodě, pokud ho kompilátor/in­terpretr přeloží tak jak má; a na tom nějaké mezery,tabulatory a konce radku nemaji vliv; z toho jak vidívám nejčastěji javaskript zapsaný – v jednom bloku bez konců řádků – tak mi přijde, že takhle je to fakt dobře přehledné.

konvence neexistují; jestli se někdo odvolává na Kerrigena a Richieho (nevim jak se přesně jmenují) s jejich knížkou o Céčku, tak jejich zápis se mi líbí; ale nenapadlo by mě, že to bude někdo vyžadovat v článcích; jen BFU potřebují předem správně zformátovaný kód, podle toho, jak se to učili ve škole, ostatní si to dokáží přeluštit nebo aspoň zkonvertovat…

Honza77

Také se přimlouvám, i když to asi nebude nic platné. Že neexistuje něco jako oficiální statistika všech existujících kódů je celkem jasné. Ale zase zde je převaha onoho „standardního“ stylu tak výrazná, že si ji všimne každý, kdo měl co dočinění s JS. Použitý styl se mi velmi obtížně čte.

Jan Pejša

Na žádná pravidla, jak formátovat ukázky kódů zde na zdrojáku, jsem nenarazil. Proto se přikláním na stranu autorů samotných – ať si to píšou jak chtějí.

Rada pro čtenáře – existují online nástroje na přeformátování kódu – například:
http://www.prettyprinter.de/
http://jsbeautifier.org/

…takže není nic jednoduššího než: označ, zkopíruj, vlož a nech si přeformátovat (zkrášlit) kód :)

Daniel Steigerwald

Článek je pěkný, tedy až na poslední odstavec ;). Jen pár poznámek: „new fn()“ versus fn() – jen s operatorem new se opravdu instancuje/vytváří nový objekt. Lidi na to zapomínají, protože to funguje i bez operatoru (blbe ale;)

Způsob, který Pixy popisuje zavedl Crockford.

Jde o hack, který pouze simuluje privátní (ve skutečnosti lokální) proměnné, víc nic. Taky kód není pěkný. A hlavně veškerá sranda skončí, až se někdo pokusí podědit/rozšířit takovou třídu.

Přesto jde o zápis užitečnej. Používá se k definici modulu (neco jako singleton). Lidi ho ale celkem oprávněně nemají rádi. Málo se totiž zdůrazňuje, že třídy se takto definovat nemají (a nedají ;).

Dovolím si nesouhlasit s posledním odstavcem.

Tento postup není svatým grálem. Je to prostě jen způsob, jak něco zapouzdřit. Žádný JS framework na tom třídy nestaví. jQuery navíc vůbec žádné třídy, či OOP nemá. Řešením OOP je prototype.

Daniel Steigerwald

… jen dodám, že „třída“ není v JS pojem přesný, avšak všichni ho používají a tak je zbytečné se nad tím hádat.

Aichi

Zdá se mi, že se bojíte nějaké magie. Použití prototypů není pouze o rozšiřování stávajících JS objektů, ale i o tvorbě objektů vlastních. Definovat „třídu“ jde např. takto

tridaA = function() {
  this.x = 5;
};

tridaA.prototype.getX = function() {
    return this.x;
};

var a = new tridaA();
alert(a.getX());

Taková definice má svoje výhody, např. funkce jsou do nových instancí odkazovány referencí a nejsou znovu a znovu vytvářeny jako ve vašem přikladě, tedy šetříme místo. Nicméně nejde použít var pro „privátní“ vlastnosti.

Daniel Steigerwald

Pixy, nesouhlasím s každou větou co píšeš ;)

„Prototype coby řešení je věcí názoru.“

Není, JS prototype je legitimní, všemi knihovnami využívaný postup. To, co popisuje tvůj článek, nemá s OOP ani s třídami vůbec nic společného.

„Někteří – včetně mě – k tomu mají vážné výhrady, prototypově-rozšiřující přístup (jako u frameworků Prototype nebo MooTools) je jim dost proti srsti“

Nechápu, proč do této diskuze zatahuješ Mootools a Prototype. Ano, tyto frameworky mají společnou jednu vlastnost → modifikují prototype nativních funkcí. To ale s tímto článkem, ani s toutou diskuzí nemá vůbec nic společného.

„a dávají rozhodně přednost zapouzření a namespacingu (jako u jQuery nebo YUI).“

jQuery ani YUI žádné vlastní supr-dupr zapouzdření, o kterém se zmiňuješ nenabízejí. Namespacing jQuery je odstrašující příklad, doslova anti-pattern, příklad, jak se to dělat nemá. Mít super-magic-uber-powerfull funkci jQuery, která umí přidat element, setřídit pole a umýt nádobí, je dobrý tak do powerpoint prezentace pro účastníky kurzu „chci mousehover, ale nechcu se učit ždejes“. Mimochodem YUI3 prototype dědičnost používá všude.

„Obecně platná pravda neexistuje, je to ryze subjektivní.“ To je nihilismus ;) Polévky se nožem nenajíš. Prostě OOP není jen o zapouzdření.

jelc

Nezlobte se, ale to co tu zmiňuje Aichi a pan Steigerwald není mimoběžné, ale jsou to relevantní připomínky k tématu. Hovořit o objektech a „třídách“ v javascriptu a nezmínit prototypování mi přijde škoda a to hlavně v případě, kdy hovoříme o oblastech působnosti proměných. V případě, že definujete konstruktory objektů s využitím prototypů, prakticky nemůžete použít první dvě ukázky. Prototypové metody nebudou znát lokální vlastnosti nadefinované v konstruktoru. Uživatel si v podstatě musí vybrat, zda jsou pro něj cenější „zapouzdřené“ vlastnosti objektů, nebo možnost šetrnějšího přístupu k paměťovému prostoru, možnost využít „dědičnost“ případně další postupy z OOP. Pokud jde o modifikaci prototypových vlastností nativních javascriptových objektů, tak snad jediné „ZLO“ je rozšiřovat prototypy od konstruktoru Objectu. Ostatně myslím si, že diskuse pod Vašimi články jsou stejně hodnotné jako samotné články, tak trochu nechápu Vaši dotčenou reakci.

v6ak

IMHO tu bude nějaký problém mezi panem Steigerwaldem a panem Staníčkem. V minulém díle byl kousek diskuze, který to naznačoval, ale už tam asi není. K tématu: To, že si musím vybrat jeden z přístupů a vybrat si jednu podmnožinu toho, co je jinde považováno za standard, je určitě taky zlo. Je snad požadavek zapouzdření spolu s možností dědičnosti tak problematický? Btw: když chci použít dědičnost, pak teprve je vidět, jak jsou potřeba privátní vlastnosti. Pokud budou privátní jen podle dokumentace, mohou dvě třídy* omylem použít stejný název. Možnost měnit metody a přidávat metody bez namespace není zlo do té doby, než toho je využito. Zlo je taky nemožnost volání nadřazeného konstruktoru. V Javě povinnost (částečně automaticky zařízená), v Javascriptu nemožnost. Dva naprosto odlišné přístupy, přičemž u druhého mi nejsou jasné výhody. Fakt nevím, proč se ten jazyk jmenuje JAVAscript. *) Tak, jak si z komponent můžu složit barák, tak si v Javascriptu můžu složit třídu. Tomuto pojmu bych se nebránil. Těžko bude někdo argumentovat, že tu není barák, ale pouze panely, omítka a mnohé další věci. Dohromady to prostě tvoří barák, i když skutečnost je složitější.

jelc

Nejsem si jistý, zda přesně rozumím, nicméně pokud v javascriptu nějakým způsobem implementujete dědičnost, není nemožné volat v „konstruktoru“ „konstruktor“ předka, jen si to musíte implementovat sám, stejně jako obecné volání nějaké metody předka, kterou potome předefinoval. Jazyk sám takovou konstrukci nemá, ale to neznamená, že to je nemožné. Tomu příkladu se stejným názvem privátní vlastnosti ve dvou různých třídách* přiznám se nerozumím, ale zajímá mne to. Jinak javascript samozřejmně není java a nemá s ní kromě podobné syntaxe nic společného a je marné očekávat od něj podobné chování, také jeho objektovost je poněkud „specifická“, ale to neznamená že by nebyla ,)

v6ak

To by mě zajímalo jak se to dá udělat. Problém s názvy vlastností je následující: Mám třídu. Ta používá vlastnosti, které nejsou pro jazyk soukromé, i když bych k nim tak měl podle dokumentace přistupovat. O soukromé vlastnosti by se logicky neměl kód mimo třídu starat. On o nich vůbec neví. Neměl by se o ně starat ani potomek. A teď té třídě udělejte potomka. O její soukromé vlastnosti se nemusíte starat, že? Výborně, pak mohou být pojmenovány stejně, ne? Pokud jsou skutečně soukromé, pak ano. Pokud jsou ale soukromé jen podle dokumentace, nastává problém. Řešením by bylo prefixovat názvem třídy i s namespace. Ale komu se do toho chce? To by chtělo převodník.

jelc

(pokud se nemýlím obecně platí že se dědí veřejné i privátní vlastnosti): Mám třídu A která definuje vlastnost z, vytvořím instanci třídy A, která se bude jmenovat „a“, k vlastnosti z se dostanu přes a.z. Nyní vytvořím třídu B, která dědí ze třídy A, tím pádem bude mít také vlastnost „z“, bude tedy fungovat že v instanci B (b) se dostanu k vlastnosti z přes b.z. Pokud třída B definuje vlastní vlastnost „z“, volání b.z vrátí tuto, budu-li chtít zavolat z definované předkem, buď mi to umožňuje jazyk, nebo se mohu pokusit o vlastní implementaci takové funkcionality. To nějakým způsobem v javascriptu lze, ale máte pravdu že si nelze vynutit „privátnost“ vlastnosti „z“ (vždy k ní bude možné přistupovat zvenčí), a její privátnost bude záležitostí předem dohodnuté konvence. Předpokládám, že různé javascriptové knihovny, které umožňují dědění musí toto řešit, já znám minimálně jednu, která to dělá. Nicméně javascript jako takový to sám od sebe neumí.

v6ak

Možná tomu rozumíte, ale váš příspěvek vypadá trošku zmateně. Především: k privátním vlastnostem nemůže přistupovat jen tak někdo, k nim může přistupovat jen sama třída. Tedy když chci přistupovat k vlastnosti z proměnné b, je vždy jasné, co se stane (předpokládám privátnost): * pokud přistupuji z třídy A, pak přistupuji k vlastnosti uvedené ve třídě A * pokud přistupuji z třídy B a třída B nemá svoji vlastnost z, pak pro ni neexistuje a je to chyba * pokud přistupuji z třídy B a třída B má svoji vlastnost z, pak přistupuji k jiné vlastnosti z než umí třída A. * pokud přistupuji odjinud (lehce zjednodušeno), je to chyba Toto chování ve v JS problematické.

Michal Augustýn

IMHO se mýlíte – dědí se jen veřejné vlastnosti, privátní ne. Některé jazyky ještě zavádí princeznu Koloběžku (protected). Takové položky jsou privátní pro všechny kromě těch, kteří z třídy dědí.
Pokud budete mít ve třídě A privátní položku z a v odděděné třídě B taktéž privátní položku z, ničemu to vadit nebude.

v6ak

Dobře, jenže jak udělat ty privátní proměnné? Pokud přes closures, pak se dostáváme k tomu, že to omezuje jiné věci. Chtěl bych vidět v JS takové dvě třídy, které mají tyto vlastnosti skutečně privátní, dědí od sebe a potomek volá konstruktor předka. Všechny tyto požadavky by měly být v OOP normální, ne?

jelc

Asi tomu už rozumím, v takovém případě je Váš požadavek neralizovatelný, jak jste správně uvedl výše, je to něco za něco.
Mně vyhovuje z OOP principů v javascriptu dědičnost na úkor zapouzdření, ukázky v článku preferují opak.

Michal Augustýn

Ano, přes closures, jinou možnost zavedení privátních položek ani neznám a i pokud volá potomek konstruktor předka, neměl by to být problém (jedná se o dvě samostatné closure). Ale možná se pletu. Pokud ne a redakce svolí, přichystám o tom článek…

karf

Není mi jasné, co vlastně kritzujete – JavaScript jako takový, nebo postupy popsané v článku? Zdá se mi, že to první. Ale nevím, jaký to má smysl pod článkem, který je určený pro lidi, kteří JS zřejmě chtějí používat. Na to se dá jen pokrčit rameny a říct – když se vám to nelíbí, tak to holt nepoužívejte.

Já bych se použití pojmu třída naopak bránil velmi. Třída je v ostatních jazycích ustálený pojem pro komplexní datový typ. Což v JS není a pro lidi, kteří na JS přecházejí, to může působit dost zmatečně. Čili za zásadní v intro článku o objektech v JS bych považoval napsat, že JS nemá třídy. Bohužel, to v článku nezaznělo.

v6ak

Asi máte pravdu, je to trošku odklon. Dobře, ta třída záleží na terminologii. Vidím tu dvě možnosti: * označit Javascript za objektový, používat pojem třída a kritizovat nedostatky tříd * pojem třída vynechat, vymyslet pro něj jiný název (Tak schválně, navrhněte lepší! Struktury to taky nejsou.) a javascript označit na neobjektový Možná bych se víc klonil ke druhému řešení, kdyby se tu stále nevynalézaly způsoby, jak třídy emulovat. To mě tak intuitivně dostalo k označení JS jako poněkud nedokonale objektového. Myslím, že si rozumíme, jen se dostáváme na hranici pojmů.

Aichi

Objektový jazyk != třídní jazyk. Tohle je vaše největší mýlka a pak uvažujete naprosto scestně. Javascript je prototypový jazyk, tedy defakto klonujete prototypové objekty.

v6ak

OK, ale znamená-li OOP zapouzdření, dědičnost a polymorfismus (s polymorfismem tu vzhledem k dynamičnosti typování nejsou problémy) a nemůžu-li použít oboje, pak je někde chyba, ne?

Michal Augustýn

Ano, třída je v mnoha jazycích označení komplexního datového typu. Pokud se máme ale bavit o programování obecně, pak třída je předpis pro to, jak se má zkonstruovat objekt (viz. např. Wikipedia nebo VŠ skripta zabývající se programováním).
Pro mě je třídou v JavaScriptu constructor function.

karf

Pokud víte o co jde, tak si to pro mě za mě třídou nazývejte. Ale pokud byste byl začátečník a teprve byste se to učil (školy nemaje), tak v tom budete mít nejspíš pěkný guláš.

Mimochodem, Wikipedia obsahuje pod heslem „Class“ odstaveček nadepsaný výmluvně „Non-class-based object-oriented programming“.

Michal Augustýn

Myslel jsem to tak, že by bylo vhodné v intru uvést věci na pravou míru. Tzn. že třída je obecně předpis pro to, jak vytvářet objekty a že v „běžných“ jazycích je reprezentována komplexními datovými typy, kdežto v JavaScriptu pomocí constructor function.

Michal

Tak to je teda pecka tvrdit, že to o čem tu Pixy píše není OOP. OOP vůbec není o třídách nebo o dědičnosti. OOP je o objektech. Dědičnost a třída jsou tu hlavně pro dosažení reusu kódu. Jsou tu pro odstraňování duplicit, které komplikují a prodlužují vývoj. Ovšem duplicity se dají vyřešit i jinak. Jaký je rozdíl mezi třídu v jave a tovární funkcí, kterou nám tady pixy naservíroval? Žádný. Javascript je práve v tomhle krásný, že mě nenutí do tříd, ale dovoluje mi udělat tovární funkci.

v6ak

Omezím se na toto: Dědičnost a třída tu nejsou jen kvůli znovupoužitelnosti kódu. Co polymorfismus? Nebo napovídání v IDE a statická typová kontrola?

Michal

Ano třída je tu také kvůli typové kontrole, což ovšem není součástí OOP. Polymorfismus je jedna z cest, která nám pomáhá v reusu kódu. Netvrdím, že je na třídě … něco špatného. Jen není možné tvrdit že třídy, dědění, polymorfismus rovná se OOP. Já například v Javě vytvořením nové třídy nepovažuji tuto třídu za typ. Typ je pro mě Interface, který ta třída implementuje. Tudíž typovou kontrolu používám interfacovou. A vidíte, další prvek Interface … ale opět Interface není OOP, je to jen prvek, který mi pomáhá v psaní OOP kódu. Tvrdit o něčem co nemá třídy a nepoužívá (třídní) dědění, že není OOP je diletantství.

v6ak

No třída má i další význam, viz třeba využití ve výjimkách, kde by to bez tříd nešlo.
K těm rozhraním: Ne vždy je to nejlepší. V případě kolekcí to bylo určitě dobře. Existují ale i případy, kdy rozhraní poskytují přílišnou volnost. Existují celkem dobré důvody, aby http://java.sun.com/…layable.html a http://java.sun.com/…ui/Item.html byly třídy bez veřejného konstruktoru. Na psaní vlastních rozšíření jsou tu po řadě Canvas a CustomItem, obě z javax.micro­edition.lcdui. S rozhraním by to v Javě takto zapouzdřit nešlo.

Jinak ohledně těch tříd: možná to je pravda (v praxi nepotřebuju znát přesnou definici OOP), nicméně existují celkem dobré důvody, aby tu třídy byly (snad nemusím rozebírat).

Michal

Samozřejmě existují dobré důvody a rušit bych je opravdu nechtěl. To jsem tím říct nechtěl. Jen se často setkávám s názorem, nejsou tam třídy? Tak to neni OOP. Já jsem rád že v každém jazyce je OOP implementováno jinak. Více to obohatí a dá lepší pohled na OOP. Někdo se naučí jeden OOP jazyk a pak podobu implementace v něm chce vidět najednou ve všech ostatních jazycích se kterýma přijde do styku.

Aichi

Teď mi není jasné na co odpovídáte a jestli to souvisí s mým komentářem. Pokud považujete používání prototypů jako samozřejmost ( xxx.prototype.yyy = function(){} ), pak mě zaráží, že to vůbec ve článku nezmiňujete a ukazujete pouze jedno „nejsprávnější“ řešení, které má i své velké nevýhody.

Je spousta frameworků, které používají pouze prototypy a namespacing a nejsou o nic horší než ostatní. Ono zlo v tom, že např. Prototype mění prototypy vestavěných objektů, je docela irrelevantní, neboť člověk by měl vědět co používá a pokud mu to nevyhovuje, tak to nepoužívat. V jazycích jako Ruby nebo Smalltalk je toto také možné a nadává na to pouze ten kdo v nich neprogramuje. Ti co je používají, tak vědí co je možné a používají to racionálně a pokud použíjí cizí kód, tak samozřejmě s jeho funkčností počítají.

ah01

Ono zlo v tom, že např. Prototype mění prototypy vestavěných objektů, je docela irrelevantní, neboť člověk by měl vědět co používá.

To ale neplatí vždy. Pokud vyvíjím svojí aplikaci a mám vše pod kontrolou, pak ano, jistě. Může se ale stát, že vyvíjím aplikaci, která musí koexistovat na jiné stránce s tamními skripty (např. nějaká knihovna – Lightbox nebo veřejné API). Pak použít Prototype nebo jinou knihovnu, která ovlivňuje nativní objekty, je přinejmenším diskutabilní a docela sobecké. Může způsobit dost problémů nejen mě, ale hlavně i tvůrci dané stránky.

Aichi

Použítí Prototype pokud jse autorem API? Použijte ho a pak to ostatní buď nepoužijí, nebo vás na support fóru umlátí, ale je to vaše a jejich volba, obě strany jsou svobodné ve výběru. Napiště že používáte knihovnu Prototype a vaši uživatelé se podřídí, nebo utečou.

v6ak

Tento přístup je trošku problematický a možná by mohl mít svoje jméno. Připomíná mi to něco jako „Však je to můj blog, můžu si tam dávat co chci!“ Vpodstatě ano, oboje je pravda. Ke svobodnému rozhodnutí neoddělitelně patří i nesení následků. Přístup „tady to máte, má to tyto problémy, takže pokud používáte např. JQuery, můžete se jít klouzat“ je sice oprávněný, ale může vést k tomu, že se na to mnozí lidé vykašlou a výsledek je mnohem méně užitečný. V tomto se mi líbí dojo. Má nějaké konflikty?

ah01
v6ak

OK, ani dojo není 100%. Zajímalo by mě, kde dojo špiní prototype vestavěných objektů. Ještě jsem se s tím nesetkal.

Aichi

jak psal Jelc, jediný problém s rozšiřováním prototypových vestavěných objektů je u Object. To že si rozšíříte např. Date, aby uměl formátovat datum vaše uživatele opravdu trápit nemusí. Nic není černobílé.

ah01

Ale jen pokud ho náhodou taky nenapadlo si objekt Date vylepšit a čistě náhodou svou metodu nepojmenoval stejně…

Aichi

A pak ví, že používá knihovnu co rozšiřuje a pojmenuje to jinak, nebo jí přestane používat. Vy se stále snažíte podsunout myšlenku, že programátor je tupý a neví co používá.

ah01

To jste mě špatně pochopil. Já nemluvím o případě, že si sám píšu celou aplikaci, to si můžu dělat, co chci a ničemu to nevadí, protože mám vše pod kontrolou. Já mluvím o situaci, kdy píši třeba něco jako AdSense. Kdyby bylo AdSense postaveno nad Prototype frameworkem, tak namohu na stránce, kde se vyskytuje použít jiný framework, který ovlivňuje nativní objekty (třeba MooTools nebo něco proprietárního). Vzájemně by se přepisovaly.

Ukázkový příklad je Lightbox (myslím ten originální), který je nad Prototypem postavený. U něho to není takový problém, prostě najdu nějakou jinou implementaci. Může ale nastat situace, kdy jinou volbu nemám a musím se podřídit zlovůli autora nějaké knihovny nebo JS API, který si řekl, že bude ovlivňovat nativní objekty, ale už se nezamyslel nad tím, jaké to může mít důsledky.

v6ak

Ne vždy si můžu dopřát ten luxus, že můj názor někdo bez ptaní odprezentuje za mě. Děkuji.

Jakub Vrána

Prosím o dodržování konvence velikosti písmen identifikátorů. Jak JavaScript samotný, tak prohlížeče dodržují konvenci, že velkým písmenem začínají jen konstrukční funkce (volané s operátorem new, v článku se jim říká třídy) a všechny ostatní identifikátory začínají malým písmenem (samozřejmě existuje i výjimka potvrzující pravidlo).

Michal Augustýn

Já o dodržování konvence jako Jakub neprosím, protože si myslím, že konvence je často věc osobních preferencí, zvyků a dohody. Nechť si každý používá, co je mu libo.
Ale jinak používám stejnou konvenci jako Jakub a podle toho, co čtu na internetu, tak je nejběženějí. Tedy všude camelCase, pouze constructor functions začínají velkým písmenem. Jo a konstanty CELÉ VELKÉ :)

Jakub Vrána

Ale klidně, jen ať to je v rámci jednoho článku konzistentní. A pravidlo „u drobných skriptů neodlišuju, u větších projektů ano“ je pro konvenci naprosto nevhodné, obzvlášť když se v rámci jednoho článku vyskytnou oba případy.

Porušování obvyklé konvence ubírá na přehlednosti, obzvlášť když se používá na všech úrovních JavaScriptu (jazyk jako takový, objekty prohlížečů, frameworky i spousta běžného kódu).

Jakub Vrána

Když mluvím o dodržování konvence samotným JavaScriptem, tak to jsou např. konstrukční funkce RegExp, Date, … a naopak normální funkce jako eval nebo parseInt. Když mluvím o dodržování v prohlížečích, tak to jsou objekty jako window, document, metody jako alert, vlastnosti jako opener a naopak konstrukční funkce jako XMLHttpRequest. Stejnou konvenci dodržuje pokud vím i většina nejrozšířenějších knihoven a překvapuje mě, že jsi tuto konvenci dosud nezaznamenal.

Tvoje konvence je naproti tomu tak nekonzistentní, že v jednom článku používáš jak proměnnou spojka, tak  Mezivysledek.

Pixiho JS dobroty

S Jakubem Vránou přesouhlasím. A kdyby jen to, Petr Staníček zde popisuje JavaScipt stejně jak mistr Babica své kulinářské „umění“ v TV. Nesmyslná terminologie, zavádějící argumentace, samá adjectiva a fakta skorem žádná. Autorovi naštvané reakce nenesou stopy jakékoliv argumentace a pokud ano, je to ještě horší. Přitom Pixy umí tak dobře psát… Takového polo-články jsou důvodem, proč lidi nemají rádi JavaScript.

Autor komentáře: Daniel Steigerwald

13. 8. 2009 15:49 redakčně upravil Martin Malý, důvod: Komentátor nebyl přihlášen, jméno bylo doplněno na vlastní žádost komentujícího.
lopata

Myslím, že místo toho svatatého grálu v posledním odstavci to chtělo trochu té zmínky o prototypování a json literálech. Takhle v tom bude mít začátečník trochu hokej a všude bude cpát ty singletony.

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.