Tento článok je pokračovaním série o dedičnosti v JavaScripte:

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;

→ Vyskúšajte

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;

→ Vyskúšajte

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;

→ Vyskúšajte

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"

→ Vyskúšajte

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

→ Vyskúšajte

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: