JavaScript očima programátora v2

Najít na internetu článek nebo dokonce seriál, který by se systematicky zabýval JavaScriptem, není nic jednoduchého - převažují články, které ukáží, jak deklarovat proměnné, v lepším případě funkce, poví něco o datových typech a tím to většinou končí. Navíc většina článků je mířena na použití JavaScriptu v rámci prohlížeče. Zřejmě i takovýto nedostatek informací vedl k tomu, že se z JavaScriptu stal jazyk, jenž je obestřen mnoha mýty, legendami a polopravdami, a mezi skutečnými programátory je značně neoblíben a/nebo nepochopen. Proto jsem se rozhodl sepsat tento článek, který představuje JavaScript z pohledu programátora, který je odkojen klasickými programovacími jazyky jako Pascal, C, C++, C# nebo Java. Článek představuje JavaScript jako univerzální jazyk, bez jakéhokoliv zaměření na nějakou konkrétní oblast nasazení (např. prohlížeče). Proč? Dnešní internet je plný rich internet aplikací, jejichž výkonnost je často limitována rychlostí JavaScriptu a proto bylo v posledních letech vynaloženo mnoho úsilí na to, aby JavaScript běhal co nejrychleji. Proto lze očekávat nasazení JavaScriptu i v non-browser úlohách, např. se nabízí použití JavaScriptu na server-side záležitosti - pak by mohla být celá RIA napsána v jednom jediném jazyce. Např. použití JavaScriptu v NoSql databázi CouchDB na psaní MapReduce funkcí je již realitou.

Úvod

Co je JavaScript?
JavaSript je jedním z dialektů standardizovaného jazyka ECMAScript. Další používané dialekty jsou ActionScript (bohatší syntaxe), JScript, InScript nebo QtScript. Ukázky v tomto článku jsou většinou otestovány v JavaScriptu verze 1.8, konkrétně implementaci SpiderMonkey, což je vůbec první implementace JavaScriptu a využívá ji např. Firefox (i když dnes v optimalizované verzi TraceMonkey). Ke SpiderMonkey je také vynikají dokumentace, kterou doporučuji strčit do záložek, pokud to myslíte s JavaScriptem vážně.
K dalšímu studiu jazyka doporučil přímo ECMAScript specifikaci, která je dostupná taky v praktické HTML verzi.
Článek popisuje i některé techniky, které se nehodí pro použití na webu, protože je ne všechny současné prohlížeče podporují. Před začátkem vývoje konzultujte minimální verze podporovaných prohlížečů s těmito pěknými tabulkami.

JavaScript je dynamický jazyk. Dynamičnost spočívá v tom, že nic nemá pevný typ (jde o slabě typovaný jazyk), objekty mohou během běhu programu měnit svoje atributy a je zde magická funkce eval, která spustí string.
JavaScript je funkcionální jazyk. Funkce lze do sebe libovolně vnořovat, přičemž vnější funkce tvoří uzávěr (closure) vnitřních funkcí.
JavaScript je objektově orientovaný, avšak beztřídní, jazyk - nemá třídy, jen objekty. Pro dědění se používají tzv. prototypy (JavaScript podporuje prototypovou dědičnost).
JavaScript neumožňuje explicitní uvolnění paměti - o řízení paměti se stará Garbagge Collector.

Datové typy (tak, jak je lze získat pomocí operátoru typeof):

Kromě funkcí a objektů jsou to všechno primitivní typy, ale umí se zaboxovat.
Při porovnání hodnot máme dvě možnosti:

Identifikátory v JavaScriptu jsou case sensitive (narozdíl třeba od ActionScriptu verze 1.0).

Funkce

Definice funkce může vypadat nějak takto:

function secti(a, b) { 
    var c = a + b; // lokalni promenna
    a += c;
    return a + b + c;
}

Jedná se o slabě typovaný dynamický jazyk, takže typy parametrů ani návratové hodnoty se neuváději, vše se vyhodnocuje až za běhu. Pokud např. a a b budou stringy, bude se funkce chovat jinak než kdybychom použili čísla.
Při volání funkce můžeme zadat libovolný počet parametrů. Pokud nějaký definovaný parametr při volání neuvedeme, bude jeho hodnota undefined. Pokud jich bude více, můžeme se k nim dostat přes pole arguments, které obsahuje všechny předané parametry (má položku length a indexuje se klasicky od nuly hranatými závorkami). Všechny parametry se vždy předávají hodnotou, není zde žádná přímá podpora pro předání parametru odkazem.
Možnost neuvedení parametrů de facto znamená, že všechny parametry jsou volitelné. Když parametrům chceme přiřadit nějakou defaultní hodnotu, dělá se to často takto:

function secti(a, b) {
    a = a || 0; // pokud je a undefined, null nebo prazdny retezec, priradi se do nej nula
    b = b || 0; 
    var c = a + b; // lokalni promenna
    a += c;
    return a + b + c;
}

Uvnitř funkce můžeme definovat lokální proměnné pomocí klíčového slůvka var, jak bylo ukázáno.

Výše ukázaný způsob zavedení funkce se nazývá function declaration (nebo také function statement) a můžeme ho poznat tak, že mezi function a otvírací závorkou je povinné jméno. Tento způsob zavedení funkce vytvoří funkci daného jména a automaticky ji přiřadí do proměnné stejného jména.
Jiným způsobem zavedení funkce je tzv. function expression (také function operator), který se pozná podle toho, že jméno funkce je v tomto případě nepovinné a zavedení funkce je součástí výrazu. To typicky znamená, že výsledek function expression přiřadíme do nějaké námi definované proměnné.

var secti = function(a, b) {
    return a + b;
};

Nyní vytváříme anonymní funkci, kterou přiřazujeme do proměnné secti. Jméno funkce můžeme uvést i v tomto případě, ale na toto jméno se můžeme odkazovat jen z těla této funkce - odnikud jinud není vidět (hodí se tak např. pro rekurzi).

var secti = function secti_blabla(a, b) { // jmeno funkce a promenne se nemusi shodovat
    return a + b; // jen tady je secti_blabla videt
};

Zjednodušeně řečeno můžeme říci, že function statement vytvoří funkci a automaticky i proměnnou stejného jména, naproti tomu function expression pouze vytvoří funkci a o uložení do nějaké proměnné se musíme postarat sami.

Každá funkce je zároveň objektem (konkrétně instancí třídy Function) a lze ji zavést (poslední, třetí, způsob) i takto: new Function("a", "b", "return a + b;")
Poslední parametr je string nesoucí tělo funkce, všechny předchozí parametry jsou názvy parametrů funkce.

Uzávěry

Funkce lze do sebe libovolně vnořovat (tj. v těle funkce můžeme definovat další funkci). Důležité je vědět, že uzávěr tvoří vždy celá funkce, ne blok příkazů uzavřený ve složených závorkách, jak je to běžné např. v C#. Tam bychom napsali kód v následujícím smyslu a vše by fungovalo.

for (var i = 0; i < poleObjektu.length; i++) {   
  var item = poleObjektu[i];
  document.getElementById(item.id).onclick = function() {   
    alert(item.name);   
  }
}

V JavaScriptu toto ale fungovat nebude, protože uzávěr se tvoří vždy jen na úrovni celé funkce, takže to, že item definujeme uvnitř těla cyklu, nám nepomůže (v C# ano).
Od verze JavaScriptu 1.7 má náš problém snadné řešení - místo klíčového slova var použijeme nové klíčové slovo let, které vytváří uzávěr na nižší úrovni než funkce.
Pokud jsme odkázáni pracovat s nižší verzí JavaScriptu, nezbývá nám než zajistit vytvoření další úrovně uzávěru - voláním další funkce v těle cyklu:

function makeAlertFunction(message) {
    return function() { alert(message); } // vracime funkci s uzaverem na funkci makeAlertFunction
}
for (var i = 0; i < poleObjektu.length; i++) {   
    var item = poleObjektu[i];
    document.getElementById(item.id).onclick = makeAlertFunction(item.name);
}

Objekty

Objekt je vlastně jen hash table, nese tedy dvojice klíč - hodnota. Hodnotou může být cokoliv, tedy i funkce. Položka se definuje prostým přiřazením: obj.novaPolozka = 5;. Pokud položka neexistuje, dostáváme při jejím čtení hodnotu undefined. K položkám se dá přistupovat dvěma způsoby:

obj.polozka = 5;
obj["polozka"] = 5;

Položku objektu lze i zrušit: delete obj.polozka;

Nyní bych rád udělal menší vsuvku a zmínil jak vypadá běhové prostředí (kontext, ve kterém náš program běží). Je to děsivě jednoduché - vše je objekt (i primitivní objekty se umí zaboxovat) a vždy se nacházíme v kontextu nějakého objektu (k němuž máme přístup pomocí všudypřítomného readonly this). Pokud jsme na top-level úrovni, ukazuje this na globální objekt (v prohlížečích je to window). Celý program v JavaScriptu je pak vlastně tělo globální funkce.

Zpět k objektům. Jak vytvořit nový objekt?

Následuje komentovaný příklad, na kterém si ukážeme i něco nového, zajímavého a užitečného, takže nepřeskakovat ;-)

// vytvorime promennou Car, do ktere priradime funkci se tremi parametry
// velke pismenko na zacatku jmena funkce indikuje, ze se jedna o konstrukcni funkci a tudiz by mela byt volana pomoci new
var Car = function(name, model, manufactured) {
    // nadefinujeme dve polozky v aktualnim this objektu
    this.name = name || 'Honda'; // pokud nebylo jmeno specifikovano, pouzije se 'Honda'
    this.model = model || 'Civic';
    
    // jsme ve funkci, takze muzeme nadeklarovat lokalni promennou
    // ta bude zcela podle ocekavani viditelna jen z teto funkce a funkci vnorenych
    // o lokalnich promennych v konstrukcnich funkcich se casto mluvi jako o privatnich polozkach objektu
    // k takovym polozkam se pristupuje jen pres jejich jmeno, tedy "made", zadne "this.made"!
    var made = manufactured;
    
    // nadefinujeme polozku, do niz priradime funkci, tedy vytvorime metodu objektu this
    // uvnitr metody muzeme pouzit lokalni promennou made
    this.howOld = function(now) { return now - made; }
    
    // stejne tak ale vidime i parametry funkce
    // parametry funkce jsou taktez oznacovany za privatni polozky objektu
    // a ze jsme pouzili stejne jmeno metody jako predtim? neva, stara se prepise
    this.howOld = function(now) { return now - manufactured; }
    
    // dve vyse uvedene metody pristupovaly k privatnim polozkam
    // takove metody se nazyvaji privilegovane
    
    // lokalni promenna muze byt jakehokoliv typu, tedy klidne funkce
    // takze toto je privatni metoda
    // k privatnim polozkam pristupujeme primo jejich jmenem
    // neprivatni polozky objektu musime prefixovat "this."
    // polozky jsou vsechno, i funkce, takze volani metody musi byt vzdy necim prefixovano
    // (v pripade tehoz objektu prefixem "this.")
    var printInfo = function () { println(this.name + " from " + made); }
    
    // vyse uvedene se da zapsat zkracene takto (drobne rozdily v implementaci tam ale jsou)
    function printInfo2() { println(this.name + " from " + made); }
    
    // je dobrym zvykem u novych trid definovat metodu toString, ktera vraci popis objektu
    // toto je public metoda, neni privatni ani privilegovana
    // vsimneme si, ze do polozky toString neprirazuji nejakou anonymni funkci jako v predchozich ukazkach,
    // ale pojmenovanou funkci - to muze pomoci pri debugu pri prochazeni stacku
    this.toString = function toString () { return this.name };
}

// vytvorime Hondu Civic s neznamym datem vyroby
var hc = new Car();
hc.howOld(); // vrati Nan (takovy ciselny undefined)
hc.toString(); // vrati "Honda"
//hc.printInfo(); // skoncilo by chybou - v konstrukcni funkci nikde nevidim prirazeni do this.printInfo

Vestavěné objekty JavaScriptu

JavaScript obsahuje pár vestavěných tříd. Jednak to jsou třídy, které zjednodušeně řečeno reprezentují zaboxované hodnotové typy (Boolean, Number, String); o třídě Function už řeč byla; a dále tu máme třídy, které nám usnadní řešení některých specifických úkolů: Date, RegExp (pro jeho vytvoření je možné použít i speciální literály uzavřené do slešů, např. var re = /ab+c/; je to samé jako var re = new RegExp("ab+c");), Math (obsahuje různé užitečné matematické funkce).

Asi tou nejzajímavější třídou je ale Array, tedy pole, konkrétně "dynamické" pole. Má položku length, která vrací hodnotu nejvyššího indexu v poli + 1. Pokud přiřazujeme do objektu pole položky s celočíselnými klíči, strkají se do pole. Protože je pole ale zároveň objekt, můžeme do něj přiřazovat i položky s nečíselnými klíči, např. pole.bla = "nazdar";

var pole = []; // to same jako new Array();
var inicializovanePole = [ 1, "bla", { name: "ja" }, 8, 5.8 ];
pole[2] = "dva"; // pole.length == 3, pole[0] == undefined, pole[1] == undefined
pole["2"] = "dva"; // stejne jako predchozi
pole.push("dalsi"); // dana hodnota se umisti na konec pole
pole[pole.length] = "dalsi"; // lamerska verze predchoziho
var dalsi = pole.pop(); // vrati posledni prvek pole a prvek z pole vynda
pole.length = 1; // nastaveni velikosti pole

JavaScript obsahuje vedle vestavěných tříd také pár užitečných funkcí. To nejzajímavější je dozajisté eval, která umí spustit JavaScriptový kód uložený ve stringu.

this

Klíčové slovíčko this je v JavaScriptu obestřeno mnoha mýty, přitom jeho fungování je docela jednoduché.
Na začátku programu je nastavené na globální objekt (v případě prohlížeče objekt window) a změnit se dá čtyřmi způsoby:

Prototypy

Vše je objekt, tedy i funkce je objektem. To je vidět v předcházejícím odstavci - funkce (reprezentovaná objektem třídy Function) má metody call a apply. Dále má ještě metodu toString(), která vrací zdrojový kód funkce (pokud je k dispozici). Objekt Function má dále položku length, která udává počet deklarovaných parametrů, a hlavně má položku prototype. prototype obsahuje položky, které jsou společné pro všechny objekty vytvořené pomocí této (konstrukční) funkce.
V praxi to funguje takto: napíšeme třeba var spz = hc.spz;. JavaScript se nejprve podívá, zda má objekt hc nějakou položku s názvem spz. Pokud ano, prostě ji vrátí. Pokud ne, podívá se do objektu Car.prototype, zda ten nemá položku položku spz. Pokud ji ani ten nemá, vrátí se undefined. Do objektu Car.prototype se dívá proto, že hc bylo vytvořeno pomocí konstrukční funkce Car.
Důležité je si uvědomit, že prototype je pořád jen jeden, bez ohledu na to, kolik objektů jsme pomocí dané konstrukční funkce vytvořili - všechny používají ten samý prototype. prototype si tedy můžeme představit jako kontejner pro statické položky třídy.
Jak je to ale s přiřazením do takové položky? To probíhá jinak, tak bacha na to. Když uděláme hc.spz = 'neco';, tak se JavaScript koukne, jestli existuje v objektu hc položka s názvem spz. Pokud ano, tak je její hodnota přepsána novou hodnotou. Pokud položka není nalezena, je v objektu vytvořena. Při přiřazování se prototype neuplatní!

var hc = new Car();
var sf = new Car("Skoda", "Fabia");

// zajistime, aby vsechny objekty vytvorene pomoci Car mely polozku spz
Car.prototype.spz = 'prvni';
println(hc.spz); // 'prvni'
println(sf.spz); // 'prvni'

// pri prirazeni se prototype neuplatnuje
hc.spz = 'druha';
println(Car.prototype.spz); // 'prvni'
println(hc.spz); // 'druha'
println(sf.spz); // 'prvni'

// samozrejme kdyz priradime do prototype...;)
Car.prototype.spz = 'treti';
println(Car.prototype.spz); // 'treti'
println(hc.spz); // 'druha'
println(sf.spz); // 'treti'

// oba objekty byly zkonstruovany stejnou constructor function
println(hc.constructor == sf.constructor); // true
// a byla to constructor function Car
println(hc.constructor == Car); // true

// zmena prototypu bez explicitniho vypsani jmena constructor function
hc.constructor.prototype.spz = 'ctvrta'; // stejne jako Car.prototype.spz = 'ctvrta';
println(Car.prototype.spz); // 'ctvrta'
println(hc.spz); // 'druha'
println(sf.spz); // 'ctvrta'

// vyse uvedene neplati jen na stringy, ale na vse, tedy i na funkce/metody

Co je vhodné umístit do prototype? Jsou to položky, které se nemění, tzn. především metody. Když totiž umístíme metodu do prototype, tak se už při každém volání konstrukční funkce (tedy vytváření nové instance) nebude metoda znovu vytvářet a přiřazovat do this, ale místo toho bude existovat jen jednou v prototype - tudíž šetříme pamětí (viz poznámka o neefektivnosti v jednom z předchozích příkladů).
Ale pozor, do prototype můžeme dát jen ty metody, které nejsou privilegované, tedy ty, které si nesahají na nějakou lokální proměnnou nebo parametr konstrukční funkce. Ono to totiž ani nedává moc smysl.

var Car = function(name) {
    // blbost!
    this.constructor.prototype.testMethod = function() { return name; }
}

Abychom měli přístup k privátním položkám, musí být přiřazení do prototype umístěno v konstrukční funkci. To ale znamená, že vytvoření oné metody a přiřazení do prototype probíhá při každém vytvoření instance třídy Car, přičemž se do prototype přiřadí funkce, která má uzávěr posledního volání funkce Car! Tedy metoda testMethod by vždy vracela jméno poslední vytvořené instance.
Stačí zapamatovat si jednoduché pravidlo - nepřiřazovat do prototype v konstrukční funkci, ale až za ní.

Kdyby někdo ale výše popsané chování vyžadoval (což se může stát, člověk nikdy neví), tak bych zde upozornil na jednu věc (vyžaduje ale znalosti z další části článku, takže tuto poznámku zatím klidně přeskočte). Privilegované položky v prototype totiž nejsou enumerabilní, tj. pokud budeme projíždět všechny položky objektu cyklem for-in, nedostaneme se k privilegované metodě. A to může způsobit problémy při implementaci vícenásobné dědičnosti. Abychom se tomuto problému vyhli, nepřiřazujeme v ukázce do Car.prototype, ale do this.constructor.prototype, což bude např. v případě volání konstruktoru z poděděné třídy BestCar znamenat totéž co BestCar.prototype. Použitím konstrukce this.constructor.prototype tedy umožníme přiřazení do prototype aktuálního objektu a vyhneme se nutnosti vyenumerovat tuto položku při kopírování z prototype do prototype.

Kromě metod můžeme do prototypů umístit i konstanty, pokud chceme k těmto konstantám přistupovat přes instance. V opačném případě je vhodnější umístit konstanty přímo do objektu konstrukční funkce, tedy např. Car.MAX_SEATS = 10;. V takovém případě musíme ale počítat s tím, že (pokud nepoužijeme nějaký trik) se k této konstantě nedostaneme přes jméno odvozené třídy, takže např. BestCar.MAX_SEATS bude undefined.

Prototypy podrobněji

Výše uvedený popis prototypů v JavaScriptu je trošku zjednodušený, avšak pro běžnou praxi dostatečný. Pro zájemce se mrkneme na prototypy podrobněji.
Specifikace ECMAScriptu mluví o vnitřní property každého objektu s názvem [[Prototype]], ke které se nedá nijak dostat (např. SpiderMonkey to ale umožňuje přes __proto__). A právě toto je ta property, přes kterou se skutečně provádí hledání položky objektu! Je užitečné znát tyto skutečnosti:

Properties

Jak jsme si již řekli, položky objektu (properties) lze definovat prostým přiřazením, tedy obj.polozka = hodnota; či obj["polozka"] = hodnota;. V moderních JavaScriptových enginech (implementující tuto vlastnost ECMAScriptu5) je tu ale ještě další možnost - property můžeme definovat pomocí speciální funkce defineProperty, kterou si hned ukážeme na příkladu:

// prvni parametr je objekt, pro ktery property definujeme
// druhy parametr je jmeno property
// treti parametr je tzv. property descriptor, coz je anonymni objekt s predepsanymi properties
Object.defineProperty(Car.property, "someProperty", {
	value: 36, // hodnota
	writable: true, // zda je mozne do property priradit
	enumerable: true, // zda bude property videt pri enumerovani pomoci for cyklu
	configurable: false // zda se da property smazat pomoci delete
});

Je to tedy velmi podobné, jako kdybychom udělali Car.property.someProperty = 36;, pouze máme větší kontrolu nad vlastnostmi definované property. Takovémuto property descriptoru se říká data descriptor.
Zajímavější jsou accessor descriptory:

Object.defineProperty(Car.property, "someProperty", {
	get: function() { return this.someValue; }, // getter (volitelny)
	set: function(value) { this.someValue = value; }, // setter (volitelny)
	//writable: true, // writable nema u accessor descriptoru smysl
	enumerable: true, // zda bude property videt pri enumerovani pomoci for cyklu
	configurable: false // zda se da property smazat pomoci delete
});

Buď getter nebo setter lze vynechat a tím docílíme read-only, resp. write-only property.
Pokud chceme získat property descriptor nějaké existující property, máme k dispozici funkci getOwnPropertyDescriptor.

Jak jsem psal, výše popsané je záležitost ECMAScriptu 5 a třeba ve webových prohlížečích je v současnosti možné toto použít pouze na DOM objekty v IE8+. Pokud potřebujeme nějaké takové chování už nyní, můžeme v prohlížečích Safari, Opeře, Firefoxu a Chrome, příp. JS enginech SpiderMonkey a TraceMonkey, použít následující konstrukce:

// definice na existujicim objektu
Car.prototype.__defineGetter__("someProperty", function() { return this.propValue; } );
Car.prototype.__defineSetter__("someProperty", function(value) { this.propValue = value; } );

// definice v ramci object initializeru
var obj2 = { 
    get someProperty() { return this.propValue; },
    set someProperty(value) { this.propValue = value; } 
};

Místo funkce getOwnPropertyDescriptor zde můžeme použít funkce __lookupGetter__ a __lookupSetter__.
A když jsme už u těch podtržítkových záležitostí, tak přihodím jednu třešňičku, kterou umožňuje SpiderMonkey. Do obj.__noSuchMethod__ můžeme přiřadit funkci, která očekává dva parametry. První je jméno funkce a druhý její parametry. Tato přiřazená funkce bude zavolána vždy, když někdo na objektu obj zavolá metodu, která není definována.

Tato stránka docela pěkně ukazuje, kde si člověk může dovolit použít jaké způsoby vytváření properties.
K properties s gettery a/nebo settery bych ještě dodal, že jsou to de facto metody, takže pokud přistupují jen k public položkám (tj. přes this), patří do prototype.

Statements

Teď si dáme trošku oddech a mrkneme na statements. Ty jsou téměř stejné jako v C, takže je netřeba moc rozebírat - máme tu for, while, do-while, break, continue, if-else, switch...
Za zmínku snad stojí jen for-in. Ten nám totiž umožní iterovat přes všechna jména položek objektu:

for(var key in someObj) {
    println(key + ': ' + someObj[key]); // klic: hodnota
}

Pokud chceme iterovat přímo přes hodnoty položek objektu, můžeme přes for each-in:

for each (var v in someObj) {
    println(v);
}

Dědičnost

A to nejlepší nakonec - klasická třídní dědičnost není JavaScriptem přímo podporována, JavaScript umí prototypovou dědičnost (vzpomeňte na fallbackování při hledání properties popsané výše). JavaScript je ale natolik ohebný jazyk, že je možné klasickou třídní dědičnost různými způsoby simulovat. Zde bych rád ukázal některé používané přístupy a přidám i nějaké svoje nápady. Nejprve předvedu řešení, která jsou k vidění na internetu a nejsou správná, a popíšu, v čem je u nich problém - i některá špatná řešení může být důležité znát.
V následujících příkladech budu předpokládat, že T1 je bázová třída a T2 je třída odvozená od T1.

Často je k vidění následující konstrukce: T2.prototype = new T1;
Toto velmi jednoduché řešení jakž-takž funguje, ale má řadu nevýhod:

Výhodou tohoto řešení je jednoduchost zápisu.

Následující řešení je zajímavější (avšak stále ne moc dobré):

var T2 = function(cpar1, cpar2, cpar3) {
    // vytvorime public polozku s nazvem parentClass, ktera ukazuje na constructor function predka
    // tim dame do T2 metodu, ktera umi zkonstruovat objekt typu T1
    this.parentClass = T1;
    // zde udelame to, pred cim jsem varoval - zavolame constructor function bez "new"
    // protoze se ale jedna o volani metody objektu T2, preda se do ni aktualni this
    // takze tato trida bude zinicializovana jako T1
    this.parentClass(cpar1, cpar2 + cpar3);
    // nyni nasleduji T2 specific zalezitosti jako obvykle
}

Výhody:

Nevýhody:

U posledních dvou nevýhod jsem sám vymyslel (heč! :)) jak se jich zbavit. Jednoduše využijeme jedné z možností, jak protlačit vlastní this do funkce.

var T2 = function(cpar1, cpar2, cpar3) {
    // zavolame constructor function T1, ale podstrcime mu aktualni this
    T1.call(this, cpar1, cpar2 + cpar3);
    // pokud maji T1 a T2 stejne parametry, lze zapis zkratit nasledovne
    //T1.apply(this, arguments);
    // nyni nasleduji T2 specific zalezitosti jako obvykle
}

Toto řešení nám krásně pořeší dědění instančních položek, ale stále nám zde zůstává problém s neděděním prototypu. Zde ukážu několik přístupů k dědění prototypu, resp. vytvoření správného prototype chainu - prostě aby se při hledání property v prototypech správně propadávalo do prototypů nadtříd.

Finální komentovaná ukázka dědičnosti

var Trida1 = function(cpar1, cpar2) {
    // instancni polozky
    this.field1 = cpar1 + cpar2;
    this.field2 = "hola";
    // privatni polozky
    var pfield1 = cpar2;
    var pfield2 = "hola hej";
    // privatni metoda
    var pmethod1 = function(par1) { println("Trida1.pmethod1 called"); };
    // metoda pristupujici k privatnim polozkam, tedy privilegovana
    this.method1 = function(par1) { println("Trida1.method1 called"); this.method2(); println(pfield1); };
    // property pristupujici k privatnim polozkam (SpiderMonkey/TraceMonkey specific syntax)
    this.__defineGetter__("prop1", function() { return pfield1; } );
    println("Trida1 contructor called");
};

// metoda (a prip. property) pracujici pouze nad public polozkami
Trida1.prototype.method2 = function(par1) { println("Trida1.method2 called"); };
// konstanty dostupne pres instance
Trida1.prototype.CONST1 = 1;
// konstanty dostupne pres jmeno tridy
Trida1.CONST2 = 2;
    
// podedime tridu
var Trida2 = function(cpar1, cpar2, cpar3) {
    // volame konstruktor predka se dvema parametry
    Trida1.call(this, cpar1, cpar2 + cpar3);
    // instancni polozka
    this.newfield1 = cpar1;
    // privatni polozka stejneho jmena jako privatni polozka v predkovi
    // diky ruznym closures zadny problem
    var pfield1 = cpar2;
    // property, ktera nam zpristupnuje privatni polozky pfield1 teto tridy (SpiderMonkey/TraceMonkey specific syntax)
    this.__defineGetter__("prop2", function() { return pfield1; } );
    println("Trida2 contructor called");
};
    
Trida1.extend(Trida2); // zretezime prototypy


// tridy nadefinovany, nyni je jdeme pouzivat
var t1  = new Trida1(58, 12);
var t2  = new Trida2(-28, -98, -5);
var t22 = new Trida2(59, 65, 158);
t1.method1();
t2.method1();
// cteni privatnich polozek stejneho jmena
println(t22.prop1);
println(t22.prop2);

Singleton

Jako bonus na závěr bych ukázal dvě možnosti, jak implementovat návrhový vzor singleton, který se často v JavaScriptu označuje jako module pattern.

// nadeklarujeme funkci, kterou hned zavolame
var Singleton1 = function() {
    // datove polozky musi byt jako lokalni promenne
    // prirazeni do this by znamenalo prirazeni do aktualniho objektu, coz muze byt ledasco
    var val1 = 1;
    var val2 = 2;
    var val3 = 3;
       
    // z funkce vracime anonymni objekt, ktery tvori verejny interface pro nas singleton
    return {
        prop1 : 2,
        funkce : function(par1, par2) {
            return par1 + par2;
        },
        get value1() { return val1; }, // SpiderMonkey/TraceMonkey specific syntax
    }
}(); // vsimnete si () - funkci ihned volame

Udělali jsme to, že jsme nadeklarovali funkci, ale nijak jsme ji nepojmenovali ani do ničeho nepřiřadili, ale hned po definici jsme ji zavolali. Funkce vrací anonymní objekt, který tvoří veřejný interface pro náš singleton a pouze přes něj můžeme přistupovat k privátním položkám singletonu. Ty jsou implementovány jako lokální proměnné oné nepojmenované funkce.

// nadeklarujeme konstrukcni funkci, kterou hned zavolame pomoci new
var Singleton2 = new function() {
    // datove polozky mohou byt privatni i public
    this.val1 = 1;
    var val2 = 2;
    var val3 = 3;
       
    this.prop1 = 2;
    this.funkce = function(par1, par2) {
        return par1 + par2;
    }
    this.__defineGetter__("value1", function() { return val1; });
};

Toto řešení funguje tak, že nadeklarujeme konstrukční funkci a hned ji zavoláme pomocí new. Zde je úroveň "zatajení implementace" o něco nižší, neboť pomocí Singleton2.constructor se dostaneme k oné nepojmenované konstrukční funkci.
Rozdíl mezi těmito dvěma řešeními je spíše kosmetický a není problém vymyslet další způsoby implementace singletonu.

Shrnutí

© Michal Augustýn (Augi) 2009 - 2010