This article has been localized into Czech by the community.
Prototypy: Dědičnost a rozšíření
V předchozím článku jsme vás seznámili s konceptem prototypů a obecně s řetězcem prototypů. Viděli jsme, jak se členové dědí prostřednictvím řetězce prototypů, ale v tomto článku se poněkud více ponoříme do konceptu prototypové dědičnosti, a jak ji můžeme použít ve spojení s existujícími objekty k rozšíření funkcionality.
Rozšiřování objektů
Nejprve se podívejme, jak můžeme použít systém prototypů k rozšíření stávajícího objektu. Může jít o váš vlastní objekt, ale pro tento příklad to ukáži rozšířením stávajícího, vestavěného objektu: objektu Array (Pole).
Objekt Array již obsahuje tolik užitečných metod, jak jsme probrali v článku o metodách pole, takže místo vymýšlení užitečné metody přidáme nesmyslnou, jen abychom ukázali, jak snadno to lze udělat. Takže bez dalšího otálení vám představím metodu hasCat() (máKočku), která vám řekne, zda vaše pole obsahuje kočku nebo ne:
Array.prototype.hasCat = function()
{
return this.includes("Cat");
};
Rozšiřuji stávající objekt Array odkazem na Array.prototype a poté jednoduše deklaruji funkci s názvem hasCat (máKočku). Všimněte si, že uvnitř funkce mohu odkazovat na instanci pole pomocí klíčového slova this, což mi umožňuje volat na něm metodu includes(), která udělá veškerou "těžkou" práci kontroly, zda pole obsahuje kočku nebo ne.
Nyní vyzkoušejme naši novou metodu:
Array.prototype.hasCat = function()
{
return this.includes("Cat");
};
let animals =
[
"Dog",
"Cat",
"Mouse"
];
alert("Array contains a cat: " + animals.hasCat());
Všimněte si, jak deklaruji běžné pole, jak jsme to v tomto tutoriálu již mnohokrát udělali, ale na této instanci pole nyní mám přístup k naší metodě hasCat() (máKočku). Po jejím deklarování mají všechna vaše pole tuto novou metodu k dispozici. To může být opravdu užitečné, kdykoli chcete přidat vlastní, uživatelsky definovanou funkčnost k existujícím objektům JavaScriptu, nebo k objektům z knihoven, které používáte atd.
Prototypová dědičnost
Jako alternativu k rozšíření existujících objektů si můžete vybrat jednoduše vytvoření vlastní verze objektu. To může být zvláště užitečné, pokud nechcete zatěžovat existující objekty, nebo pokud chcete provést drastické změny v základní funkčnosti, která by nebyla užitečná ve všech scénářích.
Například výše uvedený příklad, kde přidáváme metodu hasCat() (máKočku) do vestavěného objektu Array, nemusí dávat smysl pro všechny pole - možná potřebujete specifický typ pole pro manipulaci se zvířaty? Samozřejmě byste to mohli postavit od základu, ale to by bylo MNOHO práce a opravdu to není potřeba. Místo toho můžeme jen vytvořit novou verzi objektu pole, která využívá prototypovou dědičnost z vestavěného objektu Array.
Tento postup se také hodně používá k vytváření konkrétnějších verzí jiného objektu. Například můžete vytvořit objekt Animal (Zvíře) a pak jej rozšířit např. na objekt Dog (Pes) a/nebo Cat (Kočka), které dědí obecné chování od objektu Animal, zatímco vám umožňují přidat specifické chování psa nebo kočky k novým objektům.
Zde je příklad:
let animal =
{
numberOfLegs: 0,
greet: function()
{
return "Hello, I'm an animal! I have " + this.numberOfLegs + " legs...";
}
};
let dog = {};
Object.setPrototypeOf(dog, animal);
dog.numberOfLegs = 4;
dog.bark = function()
{
return "Woof!";
};
alert(animal.greet());
alert(dog.greet());
alert(dog.bark());
Takže máme obecný objekt Animal (Zvíře), který definuje počet nohou a funkci pro pozdravení světa. Pak vytvoříme objekt Dog (Pes) a použijeme metodu setPrototypeOf() k nastavení prototypu objektu psa na objekt zvířete a v podstatě dědící jeho členy.
Poté upravíme vlastnost numberOfLegs (počet nohou) a pak přidáme k objektu Dog specifickou metodu Bark() (Štěkání) - tato metoda nebude existovat na objektu Animal, pouze na objektu Dog, kam patří.
Jak vidíte, díky prototypové dědičnosti je velmi snadné stavět na existujícím objektu. V tomto okamžiku byste mohli přidat objekt Cat (Kočka), založený na objektu Animal, nebo pokud byste chtěli být ještě konkrétnější, můžete přidat specifické plemeno psa, které by pak mohlo být založeno a dědit od původního objektu Dog.
Co je "this"?
V příkladu výše si všimnete mého použití klíčového slova "this" (tohle). Jak jsme již dříve probírali, když interpret narazí na "this", vždy se odkazuje na objekt, který v danou chvíli zpracovává provádění.
Ale co se stane, když je metoda sdílena mezi dvěma různými objekty díky dědičnosti, jak jsme právě viděli u metody greet() (pozdravit)? Všimnete si, že v této metodě přistupuji k this.numberOfLegs v metodě greet(), ale naštěstí pro nás to JavaScript zvládá optimálním způsobem: Zatímco metoda je skutečně sdílena, vlastnost numberOfLegs může mít různé hodnoty napříč objektem animal a napříč objekty, které od něj dědí.
To také znamená, že když objekt animal (zvíře) použije this.numberOfLegs (this.početNožiček), i po zápisu do dog.numberOfLegs (pes.početNožiček), tak jsou to dvě oddělené hodnoty a JavaScript vždy ví, zda je this odkazem na animal nebo dog nebo na jakýkoli jiný objekt dědící od jednoho z nich.
Object.setPrototypeOf() vs. Object.create()
V příkladu výše používám metodu Object.setPrototypeOf() k přiřazení objektu animal jako prototypu nově vytvořenému objektu nazvanému dog. Tento přístup používám, protože jasně ilustruje, co se děje, ale jako alternativu bychom mohli použít metodu Object.create(). Umožní nám to vytvořit objekt dog a zároveň mu přiřadit prototyp jedním řádkem, takto:
let dog = Object.create(animal);
Prvním parametrem metody create() je prototyp, v tomto případě animal (zvíře). Podle dokumentace je přístup Object.create() ve skutečnosti preferován, protože umožňuje lepší optimalizaci vašeho kódu. Jedinou nevýhodou je nedostatečná podpora ve starších prohlížečích jako je Internet Explorer 8 a starší, ale jelikož tyto prohlížeče již Microsoft nepodporuje, není to větší problém.
Shrnutí
V posledních několika článcích jsme mluvili o prototypu objektu, řetězci prototypů a prototypové dědičnosti. Tento systém je modelem, který JavaScript původně používal, jako alternativu ke staršímu, objektově orientovanému, na třídách založenému přístupu, který najdete v mnoha programovacích jazycích, jak starých, tak nových.
Nicméně v novějších verzích specifikace EcmaScript, na které je JavaScript postaven, byly zavedeny třídy, pravděpodobně aby uspokojily programátory pocházející z programovacích jazyků založených na třídách. Model tříd v JavaScriptu je ve skutečnosti jen abstrakcí systému prototypů, takže můžete dosáhnout stejných věcí bez ohledu na to, který model preferujete, ale pokud jste obeznámeni s třídami, může být pro vás snazší pochopit a používat třídy v JavaScriptu místo přímého spoléhání na model prototypů.
Pokud tedy už víte o třídách, nebo se chcete dozvědět, o co přesně jde, doporučuji se podívat na další kapitolu, kde projdeme všechny aspekty této nedávné přídavné funkce do jazyka JavaScript.