Tento článok je pokračovaním série o dedičnosti v JavaScripte:
- JavaScript zvnútra 1/4: Dedičnosť objektov
- JavaScript zvnútra 2/4: Konštruktory
- JavaScript zvnútra 3/4: Prototyp konštruktora
- JavaScript zvnútra 4/4: Dedičnosť medzi triedami
Ako vyrobiť triedu Apple, ktorá bude dediť od
Fruit? Vieme ako bude vyzerať Fruit a zhruba ako
Apple:
function Fruit( color )
{
this.color = color;
}
Fruit.prototype.yellColor = function()
{
alert( this.color );
}
function Apple( color )
{
// Zavoláme konštruktor Fruit, aby inštancii nastavil farbu
Fruit.call( this, color );
}
var apple = new Apple("red");
apple.yellColor();
// Uncaught TypeError:
// Object #<an Apple> has no method 'yellColor'
A teraz ako to spojiť? Chceme aby objekt vytvorený triedou
Apple videl na metódu Fruit.prototype.yellColor().
Budeme navzájom prepájať prototype vlastnosti. Niekoho môže
napadnúť toto:
Apple.prototype = Fruit.prototype;
Lenže v tom prípade by sme prídávaním, či úpravou
Apple.prototype priamo prepisovali aj Fruit.prototype
a to nechceme. V prvej časti sme použili __proto__, to môžeme
aj teraz:
// Čokoľvek definované vo Fruit.prototype bude
// viditeľné i v Apple.prototype
Apple.prototype.__proto__ = Fruit.prototype;
Ale ako bolo písané v prvej časti tiež: __proto__
nie je štandardnou súčasťou implementácii. Vieme ho ale simulovať
tak, že medzi Apple a Fruit strčíme ešte jeden
pomocný konštruktor, jeho prototyp a inštanciu:
// Vytvoríme pomocný primitívny konštruktor `Q`
function Q() {};
// Jeho prototyp nastavíme na prototyp rodičovskej triedy `Fruit`
Q.prototype = Fruit.prototype;
// A prototyp detskej triedy nastavíme na novú inštanciu triedy `Q`
Apple.prototype = new Q;
// Opravíme referenciu na konštruktor (inak by ukazovala na Q)
Apple.prototype.constructor = Apple;

Diagram síce vyzerá hnusoprskne, ale v skutočnosti je tých
krokov podstatne menej. K Fruit.prototype sa totiž JavaScript
dostane už v druhom kroku: (new Q).__proto__, pretože (new
Q).__proto__ == Q.prototype == Fruit.prototype.
Keď rozpíšeme riadok Apple.prototype = new Q; podľa toho čo
už o operátore new vieme, zistíme, že JavaScript nám
vlastnosť __proto__ nastaví tak ako by sme radi:
function Q() {};
Q.prototype = Fruit.prototype;
Apple.prototype = new Object();
Apple.prototype.__proto__ = Q.prototype;
Treba si dávať pozor iba na to, že tento kód nám prepíše celý
Apple.prototype a preto je dôležité ho volať predtým ako
začneme do neho nastavovať metódy. Celokus, kde Apple dedí od
Fruit bez použitia __proto__ vypadá takto:
function Fruit( color )
{
this.color = color;
}
Fruit.prototype.yellColor = function()
{
alert( this.color );
}
function Apple( color )
{
// Zavoláme konštruktor Fruit, aby inštancii nastavil farbu
Fruit.call(this, color);
}
// Vytvoríme pomocný primitívny konštruktor `Q`
function Q() {};
// Jeho prototyp nastavíme na prototyp rodičovskej triedy `Fruit`
Q.prototype = Fruit.prototype;
// A prototyp detskej triedy nastavíme na novú inštanciu triedy `Q`
Apple.prototype = new Q;
// Opravíme referenciu na konštruktor (inak by ukazovala na Q)
Apple.prototype.constructor = Apple;
Apple.prototype.fallOnNewtonsHead = function()
//...
Pochopiteľne je možné si napísať jednoduchú funkciu, ktorá dedenie urobí krajšie. Takto:
function extends( Child, Parent )
{
function Q() {};
Q.prototype = Parent.prototype;
Child.prototype = new Q;
Child.prototype.constructor = Child;
}
extends( Apple, Fruit );
var apple = new Apple("red");
apple.yellColor(); // => "red"
Príklad funguje i keď inštancia apple nemá zadefinovanú
metódu yellColor(). Tá je definovaná až na triede
Fruit. JavaScript si ju však veselo nájde postupným
prechádzaním __proto__:
// JS sa najprv pozrie na objekt apple
apple
// Ak tam nie je, tak na jeho __proto__, ktoré ukazuje na Apple.prototype a to na q.prototype
apple.__proto__ = Apple.prototype
// Ak tam nie je, tak na jeho __proto__, ktoré ukazuje na Q.prototype a to zase na Fruit.prototype
apple.__proto__.__proto__ = Apple.prototype.__proto__ = Q.prototype = Fruit.prototype
Celé to však nie je iba o tom, aby JavaScript vedel danú vlastnosť
nájsť. Ide aj o možnosť zistiť o inštanciu akej triedy ide. Na to
slúži operátor instanceof. Objekt apple teda bude
inštanciou ako Apple, tak Fruit:
// Je apple inštanciou Apple?
alert( apple instanceof Apple ); // => true
// Je apple inštanciou Fruit?
alert( apple instanceof Fruit ); // => true
V súčasnosti už existuje množstvo článkov o tom, ako si zjednodušiť dedenie, či používať kopu ďalších pekných a užítočných postupov známych z iných jazykov. Úlohou tohto článku bolo odkryť čo sa deje pod kapotou. Ostatné je už na vašej chuti.
Doporučujem sa mrknúť na nedávnu trojsériu od Daniela Steigerwalda na Zdrojáku.
Predchádzajúce časti:
- JavaScript zvnútra 1/4: Dedičnosť objektov
- JavaScript zvnútra 2/4: Konštruktory
- JavaScript zvnútra 3/4: Prototyp konštruktora
- JavaScript zvnútra 4/4: Dedičnosť medzi triedami
moc pekne popsany, diky za serial
Martin, skvelý článok. Konečne niekto zrozumiteľne popísal jadro problému namiesto popisovania svojho vlastného proprietárneho riešenia. S týmito znalosťami si každý môže napísať svoj vlastný extendovací skript ktorý presne vyhovuje jeho požiadavkam.
Já dost často používám takovou tu klasiku:
Child.prototype = new Parent();
Child.prototype.constructor = Child;
je fakt, že to volání konstruktoru může občas vadit kvůli alokaci zdrojů, ale když si to člověk pohlídá tak to stačí.
Každopádně moc pěkný článek, popisující tu správnou cestu. Jinak pokud chcete nějakou minimalistickou věc, jen na dědičnost, tak doporučuji klasickou Base.js od Deana Edwardse.