Třídy, dědičnost a OOP v Javascriptu – II

V předchozím článku jsme si ukázali, jak se v Javascriptu řeší zapouzdření a objekty, ukázali si nejčastěji používané postupy a vysvětlili si, proč jsou špatné. V dnešním pokračování si ukážeme, jak se dědičnost v Javascriptu implementuje správně, pomocí prototypů.
Seriál: OOP v Javascriptu (3 díly)
- Třídy, dědičnost a OOP v Javascriptu – I 15. 3. 2010
- Třídy, dědičnost a OOP v Javascriptu – II 22. 3. 2010
- Třídy, dědičnost a OOP v Javascriptu – III 29. 3. 2010
Nálepky:
V předchozím díle jsme probrali základní pojmy OOP a dva špatné způsoby deklarace tříd: zneužití closure a Crockfordovy pokusy o privátní proměnné. Správný způsob si ukážeme nyní.
Jak už bylo řečeno, funkce v Javascriptu hraje dvě hlavní role. Buď je funkcí, nebo je konstrukční funkcí, krátce třídou. Klíčem k jedinému správnému vytváření tříd je vlastnost jménem prototype. Má ji každá funkce, a její výchozí hodnota je prázdný objekt.
var fn = function() {}; alert(typeof fn.prototype == 'object'); // true
Příklad: http://jsfiddle.net/pNQr5/
Jak ukazuje následující příklad, prototype se použije vždy, když konstruktor zavoláme operátorem new. Určuje, jaké vlastnosti má vytvořená instance mít.
var Animal = function(name) { this.name = name; }; Animal.prototype = { getName: function() { return this.name; } }; var mici = new Animal('Mici'); var kevi = new Animal('Kevi'); alert(mici.getName()); // Mici alert(kevi.getName()); // Kevi
Příklad: http://jsfiddle.net/ZT4AW/
Takto vypadá přirozená deklarace tříd, tak jak plyne z návrhu jazyka. Všimněte si, že nevytváříme žádné privilegované metody, ani neukládáme stav do closure. Kdybychom to udělali, zavřela by se nám cesta k pozdějším modifikacím i dědičnosti.
Jak funguje operátor new?
- vytvoří instanci podle prototype
- zavolá konstruktor v jejím kontextu (uvnitř funkce se na instanci odkazujeme pomocí this)
- vrátí vytvořenou instanci
Jediná správná technika vytváření instancí je spojení operátoru new a vlastnosti prototype. Proč? Zapamatujme si, že instance vytvořená operátorem new je „odrazem v zrcadle“ objektu prototype. Kdykoliv změníme prototype, přidáme metodu nebo vlastnost, změna se projeví ve všech instancích, a to i v těch již vytvořených. Následující příklad to dokazuje:
var Animal = function(name) { this.name = name; }; Animal.prototype = { getName: function() { return this.name; } }; var mici = new Animal('Mici'); var kevi = new Animal('Kevi'); alert(mici.getName()); // Mici alert(kevi.getName()); // Kevi // chci přidat syntax sugar metodu Animal.prototype.alertName = function() { alert(this.getName()); }; // hle!, metoda se přidala k již existující instanci mici.alertName(); // chci změnit getName Animal.prototype.getName = function() { return 'Mé jméno je: ' + this.name; }; // ha!, metoda se změnila všem existujícím instancím kevi.alertName(); // důkaz, že metody jsou sdílené alert(mici.getName === kevi.getName); // true alert(mici.getName === Animal.prototype.getName); // true
Vlastnost prototype je všemi instancemi jedné třídy sdílená. Jak tedy nastavujeme vlastnosti konkrétní instanci? Jednoduše pomocí tečkového operátoru. Můžeme si to představit jako kreslení rtěnkou na zrcadlo.

Jak ukazuje následující příklad, tečkový operátor preferuje instanční vlastnosti. Když v předposledním řádku vlastnost name instanci smažeme, vrací se hodnota z prototype.
var Person = function(name) { this.name = name; }; Person.prototype.name = 'noname'; var joe = new Person('Joe'); alert(joe.name == 'Joe'); // true delete joe.name; alert(joe.name == 'noname'); // true
Příklad: http://jsfiddle.net/5ALHm/
To, že je prototype sdílen všemi instancemi, svádí některé programátory k tomu, že považují prototype za „kontejner pro statické položky třídy (constructor function)“, což je omyl. Prototype je vzor pro tvorbu instancí. Statické položky se správně přiřazují pouze ke konstruktoru.
// statické vlastnosti přiřazujeme pouze ke konstruktoru Person.I_AM_CONSTANT = 42; Person.staticLookupMethod = function() {}; Person.staticArray = [];
Z čeho tento omyl pramení, je zřejmé: Každého programátora znalého klasických tříd dříve nebo později napadne napsat jasně instanční vlastnost přímo do prototype.
// takhle ne, instanční objekty do prototype nepatří var Person = function(skill) { this.skills.push(skill); }; Person.prototype.skills = []; var creativePerson = new Person('creativity'); var stupidPerson = new Person('stupidity'); // špatně, vypíše se 'creativity,stupidity' alert(stupidPerson.skills); // správně, instanční objekty až v konstruktoru var Person2 = function(skill) { this.skills = []; this.skills.push(skill); }; var creativePerson2 = new Person2('creativity'); var stupidPerson2 = new Person2('stupidity'); // správně, skills má být pouze stupidity alert(stupidPerson2.skills);
Porozumět tomuto příkladu je klíčové, pokud chceme pochopit hlavní rozdíl mezi klasickou třídou a třídou, jak ji chápe Javascript. Operátor new vytváří mělkou kopii objektu prototype. Pole skills,
definované v prototype, se tak stane de facto statickou vlastností, ale je zavádějící ji tak nazývat. Je zřejmé, že ke statickým vlastnostem nepřistupujeme přes instance. Shrňme si tedy, co kam patří:
- statické vlastnosti a metody přiřazujeme konstruktoru
- instanční metody definujeme v prototype
- instanční objekty vytváříme v konstruktoru (nebo ostatních metodách)
Přesto se názvy instančních objektů do prototype občas zapisují, avšak neinicializované, a pouze kvůli dokumentaci.
// ideálně var Person = function(skill) { this.skills = []; this.skills.push(skill); }; /** * Person skills in array of strings * @type {array} */ Person.prototype.skills = null; // pozor, inicializovat až v konstruktoru var creativePerson = new Person('creativity'); var stupidPerson = new Person('stupidity'); // správně, skills má být pouze stupidity alert(stupidPerson.skills);
Objektová dědičnost
Malá odbočka: V Javascriptu se často hovoří o objektové dědičnosti. Následující technika, myslím, pěkně ilustruje, co to znamená, když objekt dědí z objektu. V praxi ji nemá smysl používat, ale bude se nám hodit při výkladu implementace dědičnosti.
// funkce beget vytvoří poděděný objekt var beget = function(parent) { // F je pomocný dočasný konstruktor var F = function() {}; F.prototype = parent; var child = new F; return child; }; var parent = {}; // parent je objekt var child = beget(parent); // a child také // předkovi nastavím nějakou hodnotu parent.property = 'value'; // a vida, potomek ji má též (opačně to samozřejmě nefunguje) alert(child.property); // 'value'
Příklad: http://jsfiddle.net/SgXxK/
Možnost měnit instance i poté, co byly vytvořeny, je silným dynamickým prvkem jazyka Javascript. Dejme tomu, že používáme externí knihovnu a chceme opravit bug v jedné metodě. Můžeme samozřejmě přímo opravit kód. Co když ale chceme opravu zveřejnit ve fóru, ba co hůř, distribuovat v samostatném souboru jako dočasný fix? Pokud je metoda definovaná přes prototype, je oprava snadná.
SomeLibrary.SomeClass.prototype.buggyMethod = function() { // nový kód };
Co by se však stalo, kdyby konstruktor třídy SomeClass vypadal takto?
var SomeClass = function() { // takhle metody nikdy nedefinovat! this.buggyMethod = function() {}; };
Každá instance SomeClass by přepsala buggyMethod ze SomeClass.prototype. Oprava z vnějšku by byla nemožná. Museli bychom zasáhnout přímo do konstruktoru. Pokud bychom v konstruktoru definovali takto všechny metody (a takových příkladů je internet plný), uzavřeli bychom si cestu k pozdějším změnám. A hlavně bychom vytvářeli spoustu metod zbytečně, zas a znova, pro každou instanci zvlášť.
Daniel Steigerwald nabízí školení a konzultace JavaScriptu. Bližší informace zájemci naleznou na daniel.steigerwald.cz
Na co si dát u prototype pozor
Prototype je mocný nástroj, a jako každý takový, může napáchat dost škody. Ukážeme si dva příklady.
Modifikace Object.prototype je zlo
Object je v hierarchii všech typů nejvýše, ale object se používá také jako datový typ, asociativní pole, kde klíčem je řetězec (string) a hodnotou cokoliv. Zde vidíme dva způsoby, jak objekt vytvořit:
var obj1 = {}; var obj2 = new Object(); alert(obj1.constructor === obj2.constructor); // true
Příklad: http://jsfiddle.net/QDwdW/
Co se stane, když se rozhodneme, že úplně každý objekt by měl mít nějakou tu šikovnou metodu, třeba alert?
// tohle nikdy nedělejte Object.prototype.alert = function() { alert(this); }; (5).alert(); "Modifikace Object.prototype je zlo".alert();
Příklad: http://jsfiddle.net/kUTMv/
Jak vidíme, možnost přidat metodu všem objektům je fantastická. Má pouze dvě malé chyby. Za prvé tím rozbijeme všechny enumerace, a za druhé: rozbijeme tím všechny enumerace. Teoreticky jde o jednu a tu samou chybu, nicméně chyba je natolik závažná, že jsem ji považoval za nutné zmínit dvakrát.
// tohle nikdy nedělejte Object.prototype.alert = function() { alert(this); }; var styles = { color: 'red', height: 20 }; // vypíše true alert(styles.alert != null) for(var style in styles) { alert(style + ' ' + styles[style]); }
Příklad: http://jsfiddle.net/sA4Rs/
Cyklus for in při enumeraci prochází i klíče, které byly přidány do Object.prototype, takže pokud styles přiřadíme nějakému elementu, nastavíme mu krom barvy a šířky i alert, což asi nechceme. Že modifikovat Object.prototype je zlo, bylo napsáno na mnoha a mnoha místech. Bohužel, na mnoha a mnoha jiných místech, i v jinak dobrých článcích, tomu bylo naopak.
Kdy ještě může ještě být modifikace prototype nebezpečná?
Předchozí příklad naznačil, že pomocí prototype můžeme rozšiřovat existující třídy. Co kdybychom třeba poli přidali metodu each?
Array.prototype.each = function(fn, context) { for(var i = 0, l = this.length; i < l; i++) { fn.call(context || this, this[i], i, this); } }; ['a', 'b', 'c'].each(function(item) { alert(item); });
Příklad: http://jsfiddle.net/SdBhb/
Tento způsob je bezpečný pouze pokud zaručíme, že v aplikaci bude vždy jedině náš vlastní kód. Mohlo by se totiž stát, že i někoho jiného napadne přidat poli metodu each. V tom případě by byla naše vlastní implementace přepsána, případně my bychom přepsali each někomu jinému. Prohlížeč jako platforma pro vývoj webových aplikací je typicky heterogenní prostředí. Lidsky řečeno: widget napsaný nad knihovnou Mootools a jiný, napsaný pomocí knihovny PrototypeJS, spolu v jedné stránce fungovat nebudou. Existuje řada Javascriptových knihoven, ale jen tyto dvě (z těch, co stojí za řeč) modifikují prototype nativních typů.
Nativní typy
Použil jsem nový výraz, co znamená? Nativní typy jsou ty, které jsou v prohlížeči vestavěné. Vyjmenujme si ty, které se nachází ve všech prohlížečích: Array, Boolean,
. Typů je mnohem více, většinu z nich však podporuje až Internet Explorer 8. Názvy nejsou nic jiného než reference na konstruktory.
Date, Error, Function, Number, Object, RegExp, String
alert([].constructor === Array); // true
Příklad: http://jsfiddle.net/DjFus/
Pamatujme si, že jejich prototype bychom neměli modifikovat, pokud si nechceme zadělat na konflikty. Naopak zcela bezpečně lze modifikovat prototype tříd vlastních nebo tam, kde víme, že jiný než náš Javascript nepoběží (zpravidla Javascript na serveru).
Implementace dědičnosti
Konečně se dostáváme k hlavnímu tématu, implementaci dědičnosti. Jak řekneme Javascriptu, aby se třída Employee stala potomkem třídy Person? Jelikož nejsme ve Star Treku, nijak. Musíme mu to napsat. A předtím to někde vyčíst. V mnoha učebnicích i článcích je doporučován tento, sice funkční, ale jinak špatný, způsob:
// učebnicový nepraktický příklad var Parent = function() {}; var Child = function() {}; Child.prototype = new Parent(); // voláme konstruktor, to nechceme
Důležitý je poslední řádek. Vzpomeňme, co jsme si řekli o operátoru new, totiž že každá změna prototype se ihned projeví na všech instancích. Přiřadíme-li tedy instanci Parent do prototype Child, máme prototypovou dědičnost.
Proč jsem označil příklad jako nepraktický? Jak vidíme, při každém dědění se volá konstruktor Parent. To rozhodně nechceme, protože ten může třeba alokovat zdroje. Existuje lepší způsob – ale nejprve si obě třídy ukažme:
// deklarace třídy Person var Person = function(name) { this.name = name; }; Person.prototype.getName = function() { return this.name; }; // deklarace třídy Employee var Employee = function(name, salary) { // tady bych rád zavolal konstruktor Person, a předal mu name this.salary = salary; }; Employee.prototype.getSalary = function() { return this.salary; }; Employee.prototype.getName = function() { // tady bych rád zavolal přepsanou metodu Person getName };
Jak se třída Employee stane potomkem třídy Person? Pamatujete co jsme si říkali o dědění objektů? Nic nám nebrání podědit prototype, třeba pomocí takovéto funkce:
var extends = function(child, parent) { // F je pomocný dočasný konstruktor var F = function() { }; F.prototype = parent.prototype; child.prototype = new F(); }; // příklad volání extends(Employee, Person);
Kompletní příklad
Nyní si ukážeme kompletní příklad Javascriptové dědičnosti, včetně volání přepsané metody. Mimochodem, použitá technika je navlas stejná jako ta, kterou Google používá ve své vlastní, nedávno zveřejněné, Javascriptové knihovně Google Closure.
// pomocná funkce pro dědění var extends = function(child, parent) { // F je pomocný dočasný konstruktor var F = function() {}; F.prototype = parent.prototype; child.prototype = new F(); // konvence pro volání přepsaných metod child._superClass = parent.prototype; // dobrým zvykem je, aby instance odkazovala na svůj konstruktor child.prototype.constructor = child; }; // třída Person var Person = function(name) { this.name = name; }; Person.prototype.getName = function() { return this.name; }; // třída Employee var Employee = function(name, salary) { // zavoláme bázový konstruktor Person.call(this, name); this.salary = salary; }; // podědíme extends(Employee, Person); Employee.prototype.getSalary = function() { return this.salary; }; // přepisujeme metodu z třídy Person Employee.prototype.getName = function() { // voláme přepsanou metodu var name = Employee._superClass.getName.call(this); return name + ' (zaměstnanec)'; }; // vytvoříme instanci var joe = new Employee('Joe', 1000); // zkusmo přidáme metodu bázové třídě Person Person.prototype.setName = function(name) { this.name = name; }; // a teď tuto metodu vyzkoušíme na instanci Employee joe.setName('Pepa'); // všechny tyto testy musí projít alert([ joe.getName() == 'Pepa (zaměstnanec)', joe.getSalary() == 1000, joe instanceof Person, joe instanceof Employee, typeof Employee == 'function', typeof joe == 'object', joe.constructor == Employee, Employee._superClass == Person.prototype ]);
Příklad: http://jsfiddle.net/Eu8U8/
Takto se v Javascriptu správně implementuje dědičnost. Za zmínku stojí pomocná funkce extends. Javascriptu by sice pro vyjádření dědičnosti slušelo klíčové slovo, ale jak uvidíme později, obejdeme se pohodlně i bez něj. Funkce extends zakládá řetěz prototypové dědičnosti (prototype chain).
Volání přepsaných metod a konstruktorů
V konstruktoru Employee vidíme volání konstruktoru Person. V metodě getName pak volání přepsané metody. Je dobré si uvědomit, že volání přepsané metody:
var name = Employee._superClass.getName.call(this);
je pouze konvence. Klidně bychom mohli napsat:
var name = Person.prototype.getName.call(this);
… nebo bychom mohli zavolat i úplně jinou metodu:
var name = Object.prototype.toString.call(this);
Všimněme si volání metody call. Tím doslova říkáme: zavolej tuto metodu nad touto instancí. Tady bychom, stejně jako inženýři z Googlu, mohli skončit – a také pro dnešek skončíme. Téma by však nebylo kompletní, kdybychom si neřekli, jak lze zápis třídy i dědičnosti zjednodušit, jaké jsou další objektové techniky (agregace, mixování) a jak se k problému staví ostatní Javascriptové knihovny. Právě to bude námětem poslední části.
Nepřehlédněte!
Autor článku Daniel Steigerwald vystoupí s přednáškou na téma Třídy, dědičnost a OOP v Javascriptu na letošní konferenci Internet Developer Forum 2010. Přijďte si jej (a samosebou i další přednášející) poslechnout a zeptat se jich na to, co vás zajímá, ve středu 7. dubna do Národní technické knihovny (registrace nutná).
Díky, super.
Dobré ráno,
pěkný článek, dobrá práce. Kdyby byl článek komentářem, klikl bych na plus :)
Je zřejmé, že kvůli možnosti rekurzivního volání nelze zápis suplovat pomocí
this.constructor._superClass.neco
, ale přijde mi implementačně nepříjemné v každém takovém volání explicitně zmiňovat třídu (budoucí komplikace při přejmenování, refactoringu, …).Článek taky chválím, příliš nového jsem se sice nedozvěděl, ale pro spoustu lidí neznalých problematiky bude jistě užitečný. Hodnotit se dá i článek :)
K tomu uvádění názvu třídy při volání – pro pochopení principu je to explicitní uvedení dobré, v rámci vývoje si pak lze třeba superc v rámci closure definovat ručně jako superc = Person.prototype, nebo uvádím příklad z praxe pro automatizované řešení:
dr.registerClass("dr::gui::PopupInfo", "dr::gui::GuiObject", function(event, options)
{
this.z_index = dr.gui.GuiUtil.advZIndex();
superc._construct.apply(this, [ null, dr.createElement("div", {
$class: "dr-gui-PopupInfo-main",
$style: "position: absolute; z-index: "+this.z_index+";",
}), {} ]);
...
return this;
},
{
_$require: [ "dr::gui::GuiUtil" ],
_$css: [ "dr/gui/PopupInfo.css" ],
close: function()
{
...
superc.close.apply(this, arguments);
},
});
Definici superc (či dalších proměnných) nepíše vývojář ručně, ale v rámci closure přidává proces na serveru. Na klienta tak už jde značně modifikovaný kompletní a korektní javascript.
Některé javascript frameworky umožňují i more-human-friendly zápisy jako $super() (tzn. v závistlo na kontextu funkce), ale za cenu složitě definovaných proxy, které celý běh zpomalují a kromě relativně hezčího (ale často nedostačujícího) zápisu vlastně nic nepřináší.
<div class=„opin-edited-info“>22. 3. 2010 13:48 redakčně upravil Martin Malý, důvod: Úprava formátování kódu</div>
Hm, tak jsem čekal, že code bude mít i pre, takže ještě jednou:
O tom, co dělají jiné frameworky píši v dalším dílu. Souhlasím, že krom sporně hezčího zápisu nic nenabízejí. Sám jsem si s využitím closure dost vyhrál ;), ale nakonec sem jej zavrhl. Jen pro zajímavost, kód vypadal takto:
Všechny kroky byly volně definovatelné. Jak proces vytváření třídy, tak jednotlivé modifikátory.. Ale jak říkám. Tohle není fakt nutné, jen sem si hrál :)
Díky :)
1. Povaha vazby je stále stejná. Změnit nelze proto, že prostě nelze přepsat operátor new. Přesnější formulace by mohla znít: „Operátor new vytváří něco jako mělkou kopii objektu prototype, až na to, že vazba mezi prototype a vytvořenou kopií je živá.“ ;) Přesto tuším kam míříte. Lze přepsat chování konstruktoru, ten může vrátit vlastní objekt (return cokolivNeprimitivníHodnoty). Následující technika je z knihovny Mootools. Valerio se snažil „podědit“ funkci. Příklad:
Ve skutečnosti se nic nedědí, jen se tvoří generované objekty. Je to stejně špatná technika, jako dříve zmíněné. Přidám-li do prototype Class novou metodu, již vytvořené třídy zůstanou nezměněné. Prostě proto, že jsme jim vlastnosti nakopírovali. Já tuto techniku považuji za hack, dobrý leda tak k ohromení někoho, kdo neví jak Javascript ve skutečnosti funguje (MooFools ;)
2. Hehe, to je velikonoční vajíčko pro pozorného čtenáře. Nikomu jej na stříbrném podnose servírovat nebudu ,–)
3. Rekurzivní volání není možnost, ale nevyhnutelný důsledek ;) Pomocná vlastnost superClass je IMHO dostačující. Jak často přejmenováváte třídy v jednom projektu? Dvakrát? Někdo sice považuje „hard coding“ názvů tříd za ošklivý, ale stejně tak jej lze považovat za „krásně explicitní“ Je to věc rozhodnutí ;) Existuje jeden způsob (viz. další díl), kterým „to jde hacknout“. Ano, všechny metody prototypu obalit další metodou ala Dean Edwards. Já tento přístup považuji za zbytečný hack, takže ho nikde propagovat nebudu (pouze jej v dalším díle zavrhnu;)
Právě tato vlastnost, používaná při přístupu k vlastnostem objektu, je klíčová pro implementaci prototypové dědičnosti (při volání funkce s operátorem new – a jen v tomto případě – je hodnota [[Prototype]] nastavena na mělkou kopii prototypového objektu volané funkce). Proto je v řadě implementací možné (i když jde, i s ohledem na pojmenování, o nestandardní fíčuru) napsat
Osobně si myslím, že pochopení této vlastnosti/vazby je důležitým krokem při poznávání krás prototypové dědičnosti :)
Aha, jenže vlastnost __proto__ Internet Explorer nezná. Proto sem ji nezmínil. Navíc, myslím, že objektová dědičnost pomocí dočasného konstruktoru je čistší. Na druhou stranu, možná by to výkladu prospělo.. těžko říct ;)
Nejde ani tak o to, jestli nám naše implementace dovoluje __proto__ měnit, jako spíš o skutečnost, že nějaká obdobná vlastnost (i když třeba není čitelná) musí v každém interpretu existovat (neb to vyžaduje specifikace).
Nakonec je úplně jedno, jestli k [[Prototype]] smíme přistupovat nebo ji dokonce měnit. Klíčová informace je, že tato skrytá vlastnost je u všech objektů vytvořených operátorem new (to je ta hlavní odlišnost od ostatních způsobů tvorby objektů), že odkazuje na prototypový objekt funkce konstruktoru a že je použita jako (rekurzivní) fallback při veškerých přístupech k vlastnostem, které nejsou definovány přímo natvrdo v našem objektu.
Výborný seriál, konečně rozumný přístup k prototypové dědičnosti. Nemám rád, když si každá knihovna vytváří vlastní neprůhledný systém tříd. Jen opravdu malinké rýpnutí:
Píšete, že v JS je konvencí nazývat třídou konstrukční funkci. Potom se toto:
child._superClass = parent.prototype;
jeví jako trochu zavádějící pojmenování, ne?
Možná trochu. Ale pořád lepší superClass, než nějaký vlastní-ultra-krutopřísný-hustopřesný název ;) Lidi prostě class „tak nějak znají“. Tuto zkratku používá Google Closure, YUI3, a bůh ví kdo ještě. Lepší název nikdo nevymyslel, a pokud ano, tak se nezažil ;)
Výborný článok (aj celý seriál), presne v tejto oblasti som si chcel/potreboval prehĺbiť znalosti. Rád by som len upozornil na drobnú chybičku (ak tomu správne rozumiem): v časti Modifikace Object.prototype je zlo, v prvej ukážke na 3. riadku by asi malo byť
alert(obj1.constructor === obj2.constructor); // true
(jestli se nepletu :-D)Rozhodně nepletete ;) Díky za upozornění. Je to překlep.
Pekny a hodnotny clanek, diky. Jenom ty prirovnani mi tam pripadaji spis rusive. Nejak mi tam nepasujou a v obou pripadech mi vubec nepomohly k pochopeni veci. Nad mercedesem minule jsem chvili premyslel ale dneska jsem rtenku uz radsi rychle preskocil a cetl dal. Pak jsme se k ni vratil ale stejne nevim co tim autor myslel. Zbytecny balast u jinak dobreho clanku.
Že na zrcadle je vidět, to co se v něm odráží + to co je na něm nakreslené rtěnkou. A zároveň to, co je nakreslené rtěnkou není projektováno zpět na původní obraz. Já myslím, že je to pěkné přirovnání…
Tak teď jsem to konečně pochopil :-) Díky.
aha, nojo, to pak sedi. Akorat jsem myslel puvodne ze je tam to prirovnani jako napoveda a ono je to tam spis jako tajenka kterou clovek vylusti (nebo nevylusti) az popisovanou vec pochopi bez napovedy :-)
Tady bych dodal, že obecně je rozšiřování vestavěných typů zlo, ale je výjimka.
V prosinci 09 byl schválen ES5, který přidává nové metody vestavěným objektům, například indexOf u pole. V tomto případě je již jasně dané, že tato metoda je součástí jazyka a má jasně danou syntaxi i funkcionalitu, takže bych se nebránil rozšíření prototypu Array.prototype o tuto funkci pro prohlížeče, kde neexistuje
if (Array.prototype.indexOf === window.undefined)
{
Array.prototype.indexOf = function (index, fromPos)
{
//tady je kód dané funkce
}
}
Jinak pokud pomineme neshody v názvosloví (třída :) ), tak rozhodně tentokrát palec nahoru :)
Díky :), jen doplním a poopravím:
1) indexOf už pole dávno má, ve všech prohlížečích krom Internet Exploreru. Stejně bych jej ale do IE nedoplňoval, prostě proto, že většina knihoven (modifikujících prototype) jej implementuje za a) různě, za b) špatně ;)
2) Proč používáte
windows.undefined
? Občas se používá podobná technika, protože undefined lze přepsat (windows.undefined = 123
). Já sice nesouhlasím s tím, že by bylo třeba tohle nějak řešit (jestli si chce někdo rozbít undefined, prosím ;), nicméně, ani testování proti windows.undefined ničemu nepomůže. Jo a lenoši jako já, by použili tohle:No ale obecně pro <b>správnou a úplnou</b> implementaci metod, které v některých prohlížečích nefungují, bych považoval za skoro bezproblémové. Problém může nastat snad jen tehdy, když někdo z existence této metody bude usuzovat na novější implementaci a z toho na přítomnost jiných metod. Ale tady bych spíš viděl chybu v úsudku o verzi implementace.
BTW: Škoda, že nefunguje toto: http://ideone.com/3LTlt7yY
Jak moc mi první díl přišel zbytečný a špatný, tak moc se mi tenhle druhý díl líbí. Teď už je asi pozdě ten první díl úplně smazat a začít tímhle dílem, že?
Příště se, prosím, k „věci“ dostaňte už v prvním díle a nezačínejte tím, jak se to dělat nemá. Z tohoto (bohužel až druhého) dílu je vidět, že věci rozumíte a i to umíte krásně podat. Tento druhý díl skutečně dělá celému serveru velkou čest.
+1
28. // ha!, metoda se změnila všem existujícím instancím
29. kevi.alertName();
na r. 29 asi ma byt kevi.getName(); nie?
Nemá. Ale rozumím, že to nemusí být na první pohled zřejmé. Koukněte co dělá metoda alertName. Volá getName :)
Moc pěkný článek. Jen co se vysvětlování (doufám, že to tu už někdo neříkal), jak je provázaná vlastnost prototype s instancemi, popřípadě že Child.prototype=new Parent() je špatně, týče, myslím si, že dobře zpracovaný obrázek graficky znázorňující provázanost instanční vlastnosti __proto__ atd. osvětlí problém mnohem lépe.
Perfektně využil obrázky Nicholas Z. Zakas ve své knize Professional JavaScript for Web Developers. (http://jdem.cz/eajk5)
To, že __proto__ je nestandartní a IE ji nepodporuje je pro účely vysvětlení jedno.
Ten trik s vytvořením pomocného objektu a správné navázání prototype do chainu se mi líbí a neznal jsem ho (mluvím o funkci extends). První nová věc, kterou jsem se dozvěděl, ale docela zásadní ;-) Těším se na další díl! :)
Musim rict, ze jak jsem se na tento dil tesil, tak me pomerne zklamal. Kdyz jsem prvnimu dilu vytykal, ze by se mel zdrzet oznacovani nekterych pristupu za spatne, tak tentokrat bych uz vubec neuvadel popsany princip za „spravny“. Spis bych ho oznacil za jednu z cest, kterou voli nekolik frameworku, ale rozhodne ne za cistou. Mozna v nekterych ohledech zjednodusujici.
Drive nez se zacne popisovat tento zpusob, melo by byt dobre vysvetleno, jak opravdu funguji javascriptove objekty vzhledem k jeho „skoro prototypicke“ dedicnosti. Rtenka na zrcadle je sice zajimave prirovnani, ale vysvetleni principu copy on write by mozna vrhlo na dany problem vice svetla.
I mechanismus vytvareni objektu je tu popsan trochu nepruhlednym zpusobem, a tim padem zde ani neni zdurazneno, co
zpusobuje za problemy a proc se jimi vubec musime zabyvat.
Zacal bych tedy s vytvarenim objektu pomoci operatoru „new“. Ten nevytvari objekt podle prototype, jak bylo zmineno. Vytvari prazdny objekt pouze s polozkou constructor, do niz se priradi funkce, kterou predavame operatoru new jako parametr. Proto je v javascriptu mozne dat mu funkci, kterou vratime z jine funkce, pole, nebo vlastnosti jineho objektu.
Constructor ma polozku prototype, cili nam v ten moment vznika dulezita vazba object.constructor.prototype. Pote se teprve konstruktor zavola tak, jako bychom zavolali Constructor.apply(object, arguments).
Uvnitr konstruktoru a kdekoliv jinde uz se pouziji pouze dve akce. Cteni vlastnosti, nebo zapis vlastnosti. Schvalne je odlisujui, protoze kazda funguje jinak.
1. Zapis vlastnosti (napr. object.name = „Jmeno“; ) – Pokud vlastnost existuje primo na instanci, zmeni se. Jinak se na instanci vytvori, i kdyby existovala na nektereem z predku v retezci prototypu (copy on write).
2. Cteni vlastnosti (napr. alert(object.name); ) – Nejprve se vlastnost hleda primo na instanci. Pokud neexistuje, pres zminenou vazbu object.constructor.prototype se vyhleda na predkovi (prototypu), a posleze rekurzivne na vsech predcich, dokud se nenajde, nebo nezjisti, ze neni definovana.
Cili z tohoto je zrejme, ze instance a prototyp jsou nezavisle objekty spojene vazbou „prototypicke“ dedicnosti. To ze jsou nezavisle umoznuje i zmenu tohoto vztahu za behu.
Problem se skills v prikladu je tedy nekde jinde. Ne ve spatnem pouziti, ale spatnem pochopeni vazby. Protoze person.skills je objekt. Konstrukce person.skills.push(vlastnost) je tedy pouziti person.skills pro CTENI, ne pro ZAPIS, a tudiz se nezkopiruje do konkretni instance, ale pracuje se s vlastnosti prototypu.
A ted k problemu dedicnosti. Zpusob, ktery je popisovan v clanku, bych nenazval „sparvnym pro JavaScript“, zpusobem, jak prirozenou JavaScriptovou dedicnost obejit a pomoci closures (zde bych spise pouzil termin „zneuziti closures“, nez u prikladu v prvnim dile) a umele stavby dulezitych vazeb si implementovat svoji. Nic proti tomuto zpusobu, jen bych ho zdaleka neoznacil za „ten spravny“.
Zajimavejsi je, proc se k nemu doslo, tedy, co nam ta Javascriptova dedicnost tropi za problemy?
Je to jednoduche. Mixuje jeden zpusob pro vytvareni prototypu (hierarchie dedicnosti) a vytvareni novych instanci. A problem se objevi tehdy, kdyz mame konstrukcni funkce s parametry, ktere inicializuji vlastnosti objektu.
Vetsinou totiz chceme tyto vlastnosti inicializovat per instance a hlavne v momente vytvareni instance, a ne v momente definovani retezu dedicnosti. Nejmarkantneji se tento problem demonstruje u abstraktnich trid, ktere jeste ve svych konstruktorech volaji delegovane metody, ktere tutiz samy neimplementuji.
Toto neni zadna novinka pro jazyky s protoypickou dedicnosti. Je potreba si uvedomit, ze objekt v roli prototyp ma jiny vyznam, nez objekt v roli instance. Prototyp je objekt, ktery jeste neni inicializovan k obrazu a potrebam instance. Ale existuje. Byl jiz vytvoren. Je tudiz videt, ze existuji dve ruzne dulezite operace. Vytvoreni objektu a inicializace objektu.
Toto je jadro problemu a rozhodne dost dulezite na to, aby bylo v clanku popsano. Nejprirozenejsi a nejjednodussi pristup totiz je, oddelit tyto dva aspekty – konstrukci a inicializaci, a zavest dve dulezite funkce: konstruktor a inicializator.
Tento princip se pouziva v jazycich jako Self, IO, nebo dokonce SmallTalk, ktery je spise tridne instancni (btw. JavaScript byl nejvice inspirovan jazyky SmallTalk a Self).
Vytvoreni instance objektu se tudiz rozpadne na dve operace. Vytvoreni objektu pomoci konstruktoru, a jeho inicializace pomoci inicializatoru. Protoze objekty v hierarchii dedicnosti uz existuje ve forme, v jake je vytvoril konstruktor (protoypy), tak pri vytvareni instance je uz potreba pouze zavolat inicializator predka a ne konstruktor. Jednoduchy priklad:
A voila, mame prirozenou JavaScriptovou dedicnost v plne krase se vsemi vlastnostmi, ktere potrebujeme. Bez zavadeni nejakych slozitych funkci. Jedina vec je, ze jsme rozlisili dve akce, a tudiz vytvorili novou „potrebu“ obe akce volat. Ale pouze pri vytvareni instance.
Tim se meni take pohled na vlastnosti. Nejsou zde staticke a instancni, a tak neni ani dobre takove pojmy zavadet. Jsou zde prototypove a instancni. Jak si navrhneme staticke, pokud je budem nekdy potrebovat, jestli na nekterem prototypu, nebo na konstrukcni funkci, to je na libovuli kazdeho.
Troufnu si tvrdit, ze to co jsem popsal je jediny „prirozeny“ zpusob dedicnosti v JavaScriptu, tim myslim, takhle byla dedicnost pro tento jazyk navrzena. Jestli je „spravny“, nebo „nejlepsi“ v zadnem pripade netvrdim. Stejne tak bych zadne z techto hodnoceni nepouzil pro zpusoby popsane v clanku. Je to osobni preference.
Moc se mi nezdá, že new vytvoří prázdný objekt s položkou constructor. Není to spíš tak, že se položka constructor také (jako jakákoliv jiná položka) čte z prototypu?
Ale obávám se, že najdete jediný – autor spojil funkci „konstruktoru“ a „initializatoru“ do jedné. Jinak je princip zcela stejný.
Takže oba způsoby jsou ty jediné přirozené. Drobné modifikace, které si autor, Vy, či různé frameworky přidávají, nejsou v tomto směru vůbec podstatné. Použití „jediné správné“ je na místě z toho důvodu, že na rozdíl od jiných přístupů funguje se vším všudy, e.g. včetně korektní funkce instanceof apod.
K rozdílu jenom podotknu, že řešení uvedné v článku má nesporné výhody ve větší přehlednosti, pohodlnosti, kratšímu použití a mj. neduplikaci jména třídy při inicializaci.
K tomu úvodu – je to hodně o slovíčkaření, ale myslím, že i když to není takhle kompletně (a korektně) popsáno, tak je z článku celkem vidět, jak to funguje (viz příklad s delete joe.name). V každém případě, jako doplnění dobré, akorát si nemyslím, že jste s autorem nějak ve sporu ;)
Přeskočím na konec, ke statickým vlastnostem – jde čistě o názvosloví, autor používá to známé s C++ či Javy. Asi se shodnem, že definovat statické vlastnosti v rámci prototype je sice teoreticky možné, ale opět – nepohodlné, přinejmenším se musí psát jedno slovo navíc.
PS: Příklad se skills není zrovna šťastný ani v článku, ani v komentáři. V prvé řadě to totiž selže kvůli tomu, že na rozdíl od některých jiných jazyků (např. magické PHP), se pole chová konsistentně s ostatními objekty a přístup k němu či přiřazení do jiné proměnné nekopíruje jeho obsah, ale pouze referenci. Tzn. i kdyby bylo v konstruktoru this.skills = this.skills, nebo třeba this.more_skills = this.skills, tak se stále bude modifikovat pole, které je přiřazeno do prototype.
Mimochodem, při zápisu instanční proměnné se nikde technika copy on write neuplatňuje, pouze se vytváří nová položka v instanci (či přemaže stará).
Ke konzistentnímu chování: abych to upřesnil, neznamená to to, že by v PHP taková věc byla bezproblémová kvůli nekonzistentnosti, ale kvůli jiné logice OOP.
Copy on write je IMHO mnohdy čistě implementační detail a odkrýt jej nebývá jen tak a vyžaduje často určité hacky: http://gist.github.com/321601 . A JS má mnohem víc implementací.
Díky za vyčerpávající komentář.
1) Operátor new vytváří objekt přesně podle prototype. Tak to prostě je. Schválně zkuste dokázat opak ;)
2) Nerozumím, proč jedna vlastnost? Vlastností je sedm zděděných (constructor, hasOwnProperty, isPrototypeOf, propertyIsEnumerable, toLocaleString, toString, valueOf). Jedna je pouze přepsaná (constructor)
3) Copy on write? Jestli si ten který engine objekt vytvoří ihned, nebo až když jej potřebuje, to může být uživateli Javascriptu srdečně jedno, protože to stejně nemá šanci nijak zjistit ;)
4) Co se týká vaší techniky dědičnosti, hezky to za mne napsal kvr kvr http://zdrojak.root.cz/clanky/tridy-dedicnost-a-oop-v-javascriptu-ii/nazory/8172/ Nemám co dodat, snad jen, že váš způsob nepoužívá vůbec nikdo.
Poslední věta neznamená, že nejsem rád, že jste jej zde zmínil. Snad jen, že si nedovedu představit příklad, kde může mít smysl použít samostatný konstruktor, a proto definovat inicializer zvlášť. Vy nějaký takový případ znáte? Sem s ním, rád se poučím.
1) Operátor new vytváří objekt přesně podle prototype
Tohle je prostě jasně zavádějící a matoucí formulace. Je mi líto, že se musím opět ohánět specifikací, ale její výklad je v tuhle chvíli vyznamně přesnější a zároveň ilustrativnější. Operátor new vyrobí úplně normální běžný tuctový regulérní klasický prázdný objekt (stejně jako {}), jako bonus mu nastaví skrytou vlastnost a na závěr nad ním zavolá funkci konstruktoru.
2) O tom, že by constructor byla „přepsaná“ vlastnost, bych si dovolil pochybovat. Constructor je vlastnost nadefinovaná (s příznakem DontEnum) v prototypovém objektu, viz tato pozorování:
4) Nemám co dodat, snad jen, že váš způsob nepoužívá vůbec nikdo.
Škoda takové útočné a dehonestující formulace. Ohánění se zobecněními je pod úroveň pisatele odborného článku. Osobně mi zmíněná striktní separace konstruktoru a inicializátoru také nevyhovuje, ale abych si dovolil pisatele zesměšňovat (nedokazatelným) tvrzením, že jeho postup vůbec nikdo nepoužívá…
1) Nesouhlasíte s mou formulací? Dobře, vyrobte pomocí operátoru instanci, která nebude přesně podle prototype. Dokud se vám to nepodaří, má věta platí.
2) Přepsaná v prototype, ne v instanci. Proto:
a.hasOwnProperty(„hasOwnProperty“); // také false
3) Uh, kde je bod tři? ;)
4) Však také proto sem větu, větou následující, zmírnil. Avšak stále platí, že sem tento postup nikdy nikde neviděl. Proto se také pokorně ptám, proč bych jej měl využívat, protože žádnou jeho výhodu nevidím (zato nevýhod vidím dost)
1) Zde se musím zastat Ondřeje. new opravdu vytváří obyčejný prázdný objekt, tak to prostě je (viz specifikace nebo třeba implementace ve SpiderMonkey/TraceMonkey). Není jednoduché to dokázat, protože při čtení property a jejím nenalezení na instanci to automaticky fallbackne do prototype.
3) Copy on write není myšleno jako záležitost implementace, ale jako způsob, jakým se přistupuje k properties. Aneb při readu to fallbackuje do prototypu, při writu to vždy zapíše do instance (při přiřazení se prototype vůbec neuplatní).
Viz můj (já vím, nedokonalý) článek, konkrétně část „Prototypy“.
1) Vystihl jsi to přesně: „Není jednoduché to dokázat“. Ono to ve skutečnosti není ani možné dokázat. Ta operace je pro uživatele Javascriptu atomická, neviditelná, je to implementační detail. Kdybych psal specifikaci, jo to by bylo něco jiného ;)
2) Aha, už rozumím.. „tečkový operátor preferuje instanci“ není přesné, souhlasím.
1) Jo, máš pravdu, že z hlediska běžného uživatele Javascriptu můžeme tvrdit, že new vytvoří objekt stejný jako prototype.
Ale když se programátor začne zajímat třeba o paměťovou náročnost jeho řešení, tak tenhle implementační detail vyplave na povrch – pak je totiž diametrální rozdíl, jestli si drží 1000000 instancí svou kopii nějaké property nebo všechny instance sdílí jednu a tu samou property z prototype (samozřejmě do okamžiku zápisu).
Mám pocit, že tady mícháte dvě docela odlišné věci.
Prototype & prázdný objekt: to je velmi důležité chování JavaScriptu, i z hlediska „běžného“ programátora, bez něj totiž stěží prototype pochopí. Tudíž mi v článku zmínka o něm citelně chybí. A jde samozřejmě o „dokazatelnou“ věc, viz příklad s hasOwnProperty níže.
Naopak Copy on write je implementační detail, zejména u objektových jazyků téměř nepodstatný a programátora nemusí vůbec zajímat.
Ha, výborně, na hasOwnProperty jsem úplně zapomněl :)
1) S formulaci nesouhlasim z toho duvodu, ze pod ni rozumim to, ze novy objekt, ktery vytvarim podle jineho, bude mit vsechny polozky zkopirovane. Tudiz, pokud se puvodni objekt zmeni, ten novy se nezmeni. To ale pravda neni. Pokud zmenim vlastnost prototypu, projevi se zmena i v potomcich.
(ja pro zmenu vynechavam bod 2 ;) )
3) Copy on write je prave dulezity princip ze stejneho duvodu, jako chybne tvrzeni v bode 1.
Dokud totiz instance neobsahuje vlastnost primo v sobe, ale v nekterem objektu v retezu prototypu, take se projevi, pokud se tato vlastnost na danem prototypu zmeni.
4) Cekal jsem spise argumenty, ze je toto reseni pracnejsi, nebo z nejakeho hlediska horsi, a ne hned takove definitivni a navic mylne zavery o pouzivani ve „veskerem javascriptovem svete“ ;)
Nicmene rad znovu objasnim, proc je tak dulezite, alespon pro me. Protoze je to nativni zpusob, dany JavaScriptu do vinku primo jeho tvurci. Nepotrebuji k jeho fungovani zadne magicke funkce extends, zadne umele vytvareni anonymni konstrukcni funkce. Je to sice za cenu rozdeleni konstruktoru a inicializatoru, coz ovsem rozhodne neni nic proti cistote reseni, ale ma jednu nespornou vyhodu.
Kdyz vyvojar znaly JavaScrptu prijde ke kodu, ktery vyuziva nejakou vlastni implementaci dedicnosti (napr. funkce extends), jeho prvni problem bude, ze neuvidi na prvni pohled, jakym zpusobem se dedicnost realizuje. Jestli je to vas prvni, druhy, treti, nebo uplne jiny zpusob, jestli je to opravdova dedicnost pomoci prototype, nebo jen volani funkci, ktere postupne skladaji objekt?
Ve zpusobu, ktery jsem popsal, je toto naprosto jasne, a staci znalost jazyka. A prave proto bych od clanku s nazvem „Třídy, dědičnost a OOP v Javascriptu“ cekal prave ten nativni pristup. A vsechny ostatni bych uvedl az v kapitolach/dilech „Jak si javascriptovou dedicnost usnadnit/upravit/vylepsit“.
Javascript nam dava velkou volnost v tom, jak ho budeme pouzivat. Ale kdyz ji chceme vyuzit, nemuzeme zacit od konce ;)
1) Souhlas. Z tohoto úhlu pohledu sem si vědom, že ani mělká kopie není správné slovo. Právě proto sem x-krát za sebou i v příkladech opakovaně zmiňoval, že vazba je živá. Ono totiž takové slovo ani neexistuje ;)
2) dobře dobře ;)
3) Souhlas. Zkrátka jako odraz v zrcadle a rtěnka ;) Jak to vysvětlím, to už je pouze autorská licence a umělecký záměr :)
4) Však on je za mne shrnul předchozí komentující ;)
Zhruba souhlas. Však je to taky jen trade-off. Za ta léta sem si prošlapal spoustu slepých cest ;) Bez mučení ale přiznám, že jsem nikdy neuvažoval o rozdělení konstrukce a inicializace jen proto, abych se vyhnul jakékoliv pomocné funkci. To prostě pro mne už nemělo valný smysl. Osekal sem implementaci na minimum. A vysvětlil jednotlivé kroky. A myslím, že tak podrobně, že mi nikdo nemůže říct, že jsem něco podstatného zatajil ;) Dokonce i to promítnutí instance do prototype potomka tam je.
S poslední větou souhlasím. Jen dodám, že taková míra svobody je nebezpečná, a právě proto si musí nějaká autorita dupnout, a říct: „Dělejte to takhle, dokud si sami neobhájíte opak.“
PS: v dalším díle, začnu „čistý“ návrh „prasit“. Ano, i já ukážu vlastní OOP magic funkci =) Jednak z edukativních důvodů, druhak protože se může někomu opravdu hodit. On totiž ani jeden extrémní přístup není správný. Tedy, ani čistý purismus a zákaz jakékoliv pomocné funkce, ani extrémní ohnutí Javascriptu pomocí closure přes každou metodu, aka Mootools 2.0 například.
1) a 3) Ja si prave myslim, ze toto by melo byt receno nejak vice explicitne. Netvrdim, ze clovek, ktery alespon neco tusi, si to v prikladech a prirovnanich v clanku nenajde.
„A vysvětlil jednotlivé kroky. A myslím, že tak podrobně, že mi nikdo nemůže říct, že jsem něco podstatného zatajil ;)“
S touto vetou bych si prave dovolil nesouhlasit. Pokusim se nastinit proc, a co je pro me zasadni problem predstavene funkce extends. Pro poradek ji zde jeste jednou uvedu a doplnim sve komentare:
Mozna jsem jediny ignorant, kteremu unika neco zakladniho. Rozhodne neco, co v clanku zmineno neni, a treba je takovych ignorantu jako ja vice. Ale jak to vubec funguje, kdyz se v teto funkci vazba dedicnosti mezi child a parent nevytvorila?
Funguje. To si muzeme vyzkouset. A tak nezbyva, nez si vice lamat hlavu nad tim, jak je to mozne. Tedy predevsim, jak to, ze funguje dedeni a jak to, ze funguje operator instanceof.
Zkusim assci art pro nastineni problemu, co vznikne po pouziti funkce extends:
Z obrazku je videt, ze sama funkce parent stoji MIMO prototype chain objektu vytvoreneho pomoci funkce child.
A ted pro to tedy funguje. Dedicnost je jasna. Primo funkce neobsahuje metody ani vlastnosti, ktere by objekt pouzival. Cili pri vyhledavani jdeme pouze po prototypech. Protoze prototyp funkce parent uz v retezu je, pak se dostaneme na vlastnosti, ktere jsme mu dali, a tim padem to vypada, jako by byl parent sam v retezu dedicnosti. Stale upozornuji, ze neni.
A co operator instanceof? Take zafunguje. Diky necemu, co bych si ja dovolil oznacit za chybu specifikace. Ten totiz podle specifikace funguje tak, ze porovnava onen zmineny vnitrni ukazatel objektu na parenta (nekdy oznacovany __proto__, ve specifikaci pouze nepristupny [[Prototype]]) s atributem prototype funkce, ktera je druhym argumentem operatoru. Cili neoveruje, ze prave testovana funkce je v retezu prototypu, ale jeji prototype (uz je to trochu komplikovane, co? :) ). V praxi to znamena, ze tento operator vam vrati true, i kdyz vas objekt NENI potomkem testovane funkce. Cili neni spolehlivy, vlastne funguje spatne.
Takze muj zaver je: operator instanceof je chybne specifikovan. Funkce extends teto chyby zneuziva. Ve skutecnosti nevytvari vztah dedicnosti mezi child a parent, ale obalamuti tento operator a dalsi javascriptove mechanismy, aby se tak chovaly.
Muj osobni nazor je, ze toto reseni je hack, a neomlouva ho ani to, ze ho pouziva Google, nebo Yahoo ve svych knihovnach ;)
Nyni samozrejme ocekavam namitky, ze je jedno, jestli se jedna, nebo nejedna o „skutecnou“ vazbu mezi child a parent, kdyz se to ve vsech aspektech chova, jako by existovala, a nebudu mit, co proti tomu rict. To ani nebyl ucel. Ten byl:
Me uz k pochopeni, proc funkce extends funguje, chybela v clanku zasadni informace. A bez jejiho radneho pochopeni jsem k ni musel pojmout neduveru (to by predpokladam udelal kazdy, pokud neco presne nechape).
Ted uz ji chapu, a pro zmenu se mi nelibi princip, ale to uz je pouze moje osobni preference ;)
Takze muj zaver je: operator instanceof je chybne specifikovan.
To je silné tvrzení, rozhodovat o správnosti / nesprávnosti specifikace. Faktem zůstává, že operátor instanceof ve specifikaci je, všechny interprety ho implementují shodně a programátoři ho využívají. Přijde mi proto velmi přirozené psát kód, který tento operátor respektuje.
Vítejte tímto ve světě prototypové dědičnosti, kde jedna instance splňuje test instanceof pro celou řadu různých funkcí, které spolu mají jedinou věc společnou – totiž prototypový objekt. Osobně v tom nevidím pražádný problém.
V předchozím příspěvku je několikrát správně zmíněn klíčový fakt, totiž že neexistuje žádná pevná vazba mezi „instancí“ a „konstruktorem“. Skutečně, prototypový řetězec jde bokem a funkce konstruktoru je z něj odkazovaná jen dost slabou (a jak bylo již několikrát ukázáno, pochybnou) vlastností „constructor“.
Mimochodem, řešení s odděleným konstruktorem a inicializátorem se chová navlas stejně. Píšete A voila, mame prirozenou JavaScriptovou dedicnost v plne krase se vsemi vlastnostmi, ktere potrebujeme, ale vazba mezi instancí b a funkcemi A i B je zcela stejná, jako při použití extends.
+1
Nemám takový přehled, abych tvrdil něco o špatné specifikaci :), takže ke zbytku:
Berte prototyp jako definici třídy (což v podstatě je) a
Person = function(name) { this.name = name; }
jako konstruktor, pak tento přístup začne dávat logiku.Ještě k instanceof – to, že (viz výše) pracuje s prototypem (třídou) je zcela logické, stejně jako v C++ funguje
class A {}; typedef A B; dynamic_cast
.(new A)
K tomu, že v kódu při explicitním rozlišení konstruktoru a inicializátoru vidíte, co se děje pod pokličkou – to přece nikoho nezajímá. Když komunikujete s databází, tak taky začnete posílat bajty na IO síťové karty, či když řídíte auto, tak přepínáte proud na svíčky? Co jste uvedl, je dobré pro pochopení principu dědičnosti, ale nemusí být nutně dobré pro práci.
PS: Last but not least – zjistěte si, co znamená Copy on write, znovu opakuju, že ho používáte ve zcela nesmyslné souvislosti.
Ale s tim preci souhlasim, ze to co se ve skutecnosti „dedi“, cili neco jako trida, je prave ten prototyp, a ne konstrukcni funkce. O tom se nepru. Poukazuji na to, ze operator instanceof prave NEPRACUJE primo s prototypem. Pracuje s nim skryte. Az pote, co se k nemu dostane z funkce, kterou jsme mu zadali jako operand. To by bylo v poradku, kdyby platilo, ze kazda funkce ma svuj unikatni prototype. To ale jazyk primo nezajistuje. A proto zde uz ze specifikace vznika konflikt. A to je rozhodne nedostatek specifikace. Nebo mi chcete stale tvrdit opak? Muzu konfliktu predejit doplnenim sveho pristupu o umele konvence a jejich pouzivanim. To je tak vse.
Dale bych ocenil, kdybyste si uvedomil, k cemu slouzi takove clanky, jako tento. Tedy ke vzdelavani. Ano, pokud budu chtit sitovou kartu pouzivat, koupim ji, zapojim, nainstaluji ovladac a vic me nezajima. Pokud si budu cist odborny clanek o sitovych kartach, ktery se bude primo jmenovat IO sitove karty XY, pak budu cekat, ze tam prave informace o posilani bajtu a jejich vyznamu bude.
Tudiz, kdyz ctu clanek o dedicnosti v JavaScriptu, ctu ho s tim, ze se zde dozvim, na jakych principech funguje. Pokud vy ne, pak nechapu, proc jste clanek cetl cely, vcetne diskuse, a nevzal si z nej pouze funkci „extends“ ;)
Omlouvam se predevsim autorovi clanku, ze jsem se ted nechal unest skoro az k osobnimu utoku a mohlo by se zdat, ze se mi clanek vubec nelibil a ze v nem neni vysvetleno nic. Tak to neni. Vetsina veci je v nem vysvetlena pekne. Jen mi jedna zasadni vec chybela a druha mi prisla v textu popsana ne dost vystizne.
K osobnimu utoku na kvr kvr, ne na autora
Ad 1: Ok, částečně souhlasím, instanceof bere jako operand trochu nelogicky konstruktor. Nicméně s konvencemi bych to neviděl tak tragicky, ten „správný“ zápis je dost přímočarý a to, že jazyk dává možnost některé věci přiohnout, bych neviděl vždy jako zápor. Samozřejmě, když se toho chytne nějaký hacker s přehnaným sebevědomím, může to dopadnout špatně, ale to je holt všude…
Ad 2: Narážel jsem na tohle:
Muj osobni nazor je, ze cistsi je to, co je jasne a plne pod programatorovou kontrolou. Cili kdyz jasne vidim, co se deje pri konstrukkci prototypu, nebo jineho objektu, a co se deje pri jeho inicializaci. Cokoliv, co mi tento princip skryva a oddaluje, je spis hack.
Pokud jste to myslel pouze v rámci toho, že to mělo být uvedeno v článku, pak ok, souhlasím. Ale chápal jsem to, jako obecný příklad do praxe.
2.2: Funkci extends (no spíš něco daleko silnějšího) jsem si napsal před půl rokem, kdy jsem se začal JS zabývat. Článek i diskusi beru jako užitečnou věc v rožšiřování rozhledu, kde byl i přes některé chyby přínosem i Váš první příspěvek.
Ad 3: Osobní útoky jsem nezaznamenal a doufám, že ani ne z mé strany…
Ad 2: Aha, ja zas nabyl dojmu, ze to podle vas nepatri ani do clanku. V tom pripade jsme za jedno. V clanku by to podle me byt vysvetleno melo, ale v praxi si kazdy vybere, co mu nejlepe vyhovuje.
Ad 2.2: Rozsirovani rozhledu se rozhodne clanku ani diskusi neda uprit. Me osobne se nektera zakouti JavaScriptu vyjasnila, at uz diky prizpevkum v diskusi, nebo dohledanim ve specifikaci a jinych materialech, obcas i se zjistenim, ze jsem se take v leccems mylil.
Ano souhlasim se vsim :)
Tvrzeni o spravnosti bylo silne, nebo spis spatne.
Problem je v tom, ze javascript nema ciste prototypickou dedicnost, coz zpusobuje, ze se v tom clovek ztrati.
V ciste prototypicke dedicnosti vytvarim vztavy primo mezi objekty (napr. v IO pomoci Object.clone), a pokud se budu ptat na neco jako „instanceof“, budou oba parametry objekt. V tom neni problem.
Jenze v JavaScriptu se instanceof pta ne objektu, ale funkce, ktera ovsem jaksi v retezci prototypu neni. Jak spravne pisete (a ja psal v prvnim komentari spatne), objekt nema po vytvoreni vazbu na funkci, kterou byl vytvoren, ale pouze na jeji prototyp. A tak se vlastni testovani musi na objekty prevest, coz zmineny operator udela pomoci vyhledani prototypu funkce. No a to je zdrojem meho zmatku (mozna ze by se naslo vice takto zmatenych programatoru, kteri by ocenili vysvetleni tohoto principu pred jeho pouzitim).
A mate pravdu a ja ani nikde netvrdil, ze funkce exteds a prime prirazovani prototypu se chova jinak. Chova se stejne. Ale rozdil zde je. Bohuzel se fyzicky vytrati, a zbyde jen semanticky, tudiz nerozlisitelne. Kdyz se podivate do meho predchoziho prizpevku na acsii art graf :) bude v pripade primeho prirazeni parent (object(Function)) na miste f(object(Function)), cili BUDE v primem retezu vytvareni, bohuzel ne prototypu. Znamena to, ze prototyp byl opravdu vytvoren touto funkci. A to, ze se tato vazba vytrati, opravdu povazuji za slabe misto primo jazyka.
Myslím, že si špatně vysvětlujete účel funkce extends. Vy píšete „Funkce extends, od ktere ocekavame, ze vytvori vazbu dedicnosti mezi child a parent.“, kdežto Dainel píše ve článku „Nic nám nebrání podědit prototype, třeba pomocí takovéto funkce“.
Tedy účel funkce extends je jen provázat prototypy. O správnou inicializaci instančních členů se postará volání Person.call(this, name);.
K tomu vašemu řešení. Dejme tomu, že mám nativní třídu File, která vyžaduje, aby měl konstruktor (minimálně) jeden parametr, který obsahuje jméno souboru k otevření, jinak failne.
Pak Vaše volání „child.prototype = new parent();“, příp. „B.prototype = new A();“ v definici dědičnosti taky failne.
Samozřejmě pokud dojde k rozdělení na konstruktor a inicializátor, tak tenhle problém odpadne, ale toto rozdělení se mi vůbec nelíbí, protože programátor je při vytváření instancí nucen používat dost nepřirozenou syntaxi (to je to jediné, co se mi na Vašem přístupu nelíbí, ale je to pro mě dost zásadní).
Ano, tohle je argument na miste: „ale toto rozdělení se mi vůbec nelíbí…“ a takove jsem cekal spis.
„Tohle bych nepouzil, protoze je to vic psani, protoze bych musel pouzivat konstrukce, na ktere nejsem zvykly, protoze…“
Ja bych zas nepouzil fci extends, protoze skryva skutecny vztah, nebo protoze vyuziva vedlejsich efektu, nebo protoze… :)
A z toho lze vyvodit jedine. Zadny z techto zpusobu neni „lepsi“ nez ten druhy a tudiz „ten spravny“. Je vzdy lepsi v necem. A tak bychom se meli zdrzet takoveho hodnoceni a zustat u osobnich preferenci ;)
Nebo s tim nesouhlasite?
ad 1) zkusit dokázat opak? To je poměrně snadné:
Proc nikdo neodpovi? :-)
Na přání mu bylo dokázáno – k tomu nelze už nic dodat. Maximálně omluvu za silná tvrzení. I z komentářů k minulému článku mi to ale připadá, jakoby Dan (promiň, Dane) hledal mermomocí argumenty pouze pro svá tvrzení, aniž by se snažil zjistit, proč někdo tvrdí opak. Ono přesně tyhle výroky, které Dan má, rozhodně nezvyšují jeho věrohodnost – a to zvláště poté, co se vyvrátí.
Tím se nesnažím potopit Dana, určitě má za sebou spoustu projektů a zkušeností, ale je to pouze doporučení pro příště ohledně vyjadřování – více ověřovat, méně tvrdit.
Vyjasněno, už vím kde došlo ke sporu. Za prázdný objekt jsem považoval objekt, který nemá žádné vlastnosti. Ani vlastní, ani ty z prototype (krom zděděných z Object). Jenže někteří pochopili prázdný objekt jako ten, který nemá pouze vlastní vlastnosti.
Možná sem měl napsat: Operátor new vytvoří prázdný objekt, a dokud nemá své vlastnosti, linkuje ty z prototype.
Doufám, že spor je již objasněn :-)
Na Vaší ukázce dědičnosti je blbé to, že v definici dědičnosti se volá new A(). Pokud je A nějaký můj nativní objekt, který třeba otevírá file handle, tak je to pěkně na p…
Daniel ve článku dělá prakticky to samé, co Vy, pouze se vyhýbá výše zmíněnému volání new A(). Místo toho vytváří pomocnou konstrukční funkci a vytváří pomocí ní instanci, což je IMHO čistčí…
Myslim, ze jsem spise zvolil nevhodne shodne nazvy konstruktoru a inicializatoru. Pokud dam inicializatorum prefix init (tedy initA a initB), pak snadneji ukazu, ze v tomto neni mezi obema resenimi rozdil. Jen je potreba chapat posun vyznamu.
V Danielove prikladu je anonymni funkce konstruktor, a funkce, kterou definuje jako „tridu“ je jen inicializator.
V mem prikladu je konstruktor funkce A a inicializator funkce initA. Vas filehandle muze byt instancni, nebo prototypova vlastnost, a podle toho jej pouze umistite do prislusne funkce. Pocitam, ze jste mel na mysli instancni, cili umistit do initA.
Navic tu ale mate moznost prototypove vlastnosti inicializovat v konstruktoru, coz naopak v Danielove prikladu vubec nemate.
BTW. nevidim ani v cem je vytvareni pomocne anonymni funkce cistsi? Muj osobni nazor je, ze cistsi je to, co je jasne a plne pod programatorovou kontrolou. Cili kdyz jasne vidim, co se deje pri konstrukkci prototypu, nebo jineho objektu, a co se deje pri jeho inicializaci. Cokoliv, co mi tento princip skryva a oddaluje, je spis hack.
Navic tu ale mate moznost prototypove vlastnosti inicializovat v konstruktoru, coz naopak v Danielove prikladu vubec nemate.
Vůbec nevím, jaké by mělo takové počínání smysl :)
Děkuji panu Steigerwaldovi za imho jeden z nejlepších českých článků o javascriptu.
Zároveň bych chtěl odkázat na neméně výborný článek Steva Yegeho: The Universal Design Pattern http://steve-yegge.blogspot.com/2008/10/universal-design-pattern.html, který nás čtivě provede ideovými základy prototype systému, a ukáže jeho cestu napříč různými jazyky a prostředími.
Trošku odbočím: nevíte, proč nejde v IE vytvářet objekty, jejichž prototypem je DOM element? Např:
Protože IE<8 neimplementuje DOM/BOM objekty pomocí Javascriptu, ale nějakým vlastním způsobem. IE nemá nic jako Element.prototype. Zajímavé je, že jsou asi tuším tři objekty, které prototype skutečně mají Select, Option, a Image ;), takže můžeš napsat i v IE Image.prototype.fn = .. Jenže i tyhle tři prototypy jsou jen nějakej polofunkční interface.
Jasně, ale to odpovídáš na jinou otázku. Použití
F.prototype = document.getElementById(‚name‘)
mi nefunguje ani v IE 8.
Jake by bylo dalsi pouziti objektu vytvoreneho pomoci new F()?
No vida. Ono to nefunguje nejen v IE8, ale ani jinde. Do hloubky sem to nezkoumal, protože podpora IE je pro mne nutnost. Každopádně, pro lepší práci s elementy se používá wrapper (YUI3), util metody (YUI2), nakopírování metod do elementu (mootools), flyweight pattern (extjs).. asi proto, že DOM/BOM objekty nejsou toho samého typu, jako Javascriptové. Co se týká dědičnosti kterou tu uvádím, tak ta nefunguje ani pro některé vestavěné typy. Pole, string, funkci také nepodědíš. Pole třeba ano, ale v IE nebude správně fungovat length.
„The truth is that host objects are allowed to implement completely bizarre behavior and do not even have to follow rules that native JS objects follow (you can find dozens of such examples in real life).“ http://stackoverflow.com/questions/1489738/how-to-inherit-from-the-dom-element-class/1494316#1494316
Doporučuju mrknout na tohle video http://live.visitmix.com/MIX10/Sessions/CL29 někde v části, kde se probírá Marshaling (kolem 30. minuty) je to celkem slušně vysvětleno.
Pomerne hezky clanek je take zde:
http://joost.zeekat.nl/constructors-considered-mildly-confusing.html
Je sice zameren jen na dilci problem vlastnosti constructor, ale doplnen peknymi grafy pekne ilustrujicimi diskutovane vztahy objektu a konstrukcnich fci.
Existuje ještě další přístup k implementaci „dědičnosti“:
Tento postup nevytváří vazbu mezi parent – child, ale funkce f je mezi nimi sdílena, dokud není na jednom z nich přepsána. Pokud nechceme měnit prototypy za běhu skriptu a dědíme velké množství objektů, tak bych použil právě tento postup, protože volání metod vzdálených rodičů zde bude rychlejší.
Co si o tomto postupu myslíte?
Možností, jak implementovat dědičnost v JavaScriptu existuje nespočet a další budou jistě předvedeny v dalším díle článku.
Vámi uvedené řešení bych dnes asi spíš nepoužil (přitom ho sám popisuju ve svém článku, ale člověk se stále učí ;-)) a dal bych přednost implementaci z článku.
To proto, že v JavaScriptu nebudou už properties objektu jen tupé zapisovače/vkládače hodnot, ale bude možné specifikovat gettery a settery, tedy zapisovací a čtecí funkci pro každou property. Ve Vaší implementaci by se pak nakopírovaly získané hodnoty properties, ne gettovací/settovací funkce.
Výše uvedené gettery a settery jsou dostupné už teď v TraceMonkey/SpiderMonkey (__defineGetter__, __defineSetter__).
Připravovaná specifikace nového ECMAScriptu ale přistupuje k definici getterů a setterů trošku jinak. A podle tohoto nového standardu to implementuje IE8, bohužel ale jen pro DOM objekty.
Zkrátka a dobře, kopírování properties bych se při širším použití na webu vyhnul; v nějakých specifických nasazení to může mít ale své opodstatnění.
Existuje pouze jeden princip dědičnosti v JS. Ten, který jsem popsal. Příklad, který uvádíte, není dědičnost, a je chybou jej tak nazývat. Zkuste přidat toto:
Metoda ‚b‘ není definována, protože řetěz prototypové dědičnosti nevytvoříte nakopírováním metod. Váš příklad je jen další variantou generování podobných objektů.
Tak, modifikaci prototype zaživa považuju stejně celkem za prasárnu, ani ji nepovažuju za důležitou pro definici dědičnosti, takže proti tomu (možná kromě specifikace) nic.
Ta implementace má ale jiný problém – nebude fungovat operátor instanceof, a to je pro dědičnost problém dost zásadní…
Presně tak, rozbití instanceof je největší slabinou této realizace.
Dále zde vidím otravnou nutnost zmíněný cyklus vykonávat pro všechny třídy, které mají rodiče. Řekl bych, že je to díky tomu i výkonově horší.
Uvedený příklad je ale hezkým řešením pro situace, kdy chceme napodobit vícenásobnou dědičnost, resp. rozhraní. V takové chvíli nepotřebujeme fungující instanceof a předvedená varianta by se dala použít.
Přesně tak – vícenásobná dědičnost je přesně ten speciální případ, kdy (asi) nic jiného než kopírování properties nezbývá.
Ale fuj, měnit prototype zaživa se mi také nelíbí ;-) Je k tomu nějaký praktický důvod? Přijde mi přirozené a přehledné mít kód rozdělený do deklarační a výkonné části…
No tak v ideálním světě s hopsajícími růžovými poníky a dvojnásobnou porcí jahodové zmrzliny, to opravdu není pěkné. Život však není prost špinavých malých detailů, které přes svou nicotnost, dokáží zbořit sebepevnější domek (z karet). Konec poetiky a teď, k čemu je to dobré.
Praxe: Chci opravit něco v nějaké třídě, nebo jí přidat nějakou funkcionalitu. Třídu sem nepsal já, nechci hmatat do jejího kódu. Svou funkcionalitu chci třeba kamarádům ukázat ve fóru, nebo distribuovat jako dočasný patch. To všechno mi prototypová dědičnost umožňuje. Že mohu oprávněně chtít, mít jeden kus kódu tady, a druhý jinde, je prostě praxí osvědčený fakt. I proto vznikly v C# partial classes a methods. Javascript na to žádné klíčové slovo nepotřebuje. To je na něm krásné.
Teorie: Kopírováním vlastností neimplementujeme vícenásobnou dědičnost. Z velkou nadsázkou by se dalo říct, že ji pouze imitujeme. Javascript nemá vícenásobnou dědičnost. Operátor instanceof nelze donutit tvrdit, že instance je potomkem více nezávislých předků. To, o čem se v Javascriptu nesprávně hovoří jako o vícenásobné dědičnosti, a co se implementuje kopírováním vlastností do prototype, je pouze dynamická forma agregace → mixování. Viz další díl.
Nedalo mi :)
„In theory there is no difference between theory and practice. In practice there is.“
Poetika na úvod je božíí :))
Ano, duvody existuji :) Staci se podivat, co je ten nejzasadnejsi princip v OOP. Tim je polymorfismus. Ten ma ruzne formy a na nektere se dost zapomina. K cemu slouzi?
Casto je za polymorfismus povazovana jedna z jeho forem. Nazveme ji typovy/tridni polymorfismus. Znamena, ze objekt odpovi na poslanou zpravu podle toho, „jaky je“ (k jake tride patri, ci je potomek, jakou ma implementaci metody).
Polymorfismus ale neni jen tato forma. Ma jich vice. Dalsi dulezita forma je „Stavovy polymorfismus“. Objekt odpovi na poslanou zpravu podle toho, „v jakem je stavu“. V javascriptu mame paradni moznost zmenu stavu provazet prave zmenou chovani – zmenit za behu metodu.
No a ohromne kouzlo je prave v tom, ze pokud to udelame na prototypu, pak jednoduse zmenime chovani cele skupiny objektu (vsech potomku).
Dalsi velke kouzlo je, ze muzete pro jeden objekt, nebo tridu zmenit chovani oproti ostatnim, a pak se zase vratit (nastaveni a nasledny delete pro metodu, kterou obsahuje i prototyp).
Jdou s tim delat hotove cary.
Proto jsem dal dědičnost do uvozovek (nevím jestli je nějaká přesná definice dědičnosti, jestli ano, skoro bych si tipnul, že by se do ní uvedený způsob vešel..). Napsal jsem, že je to použitelné pokud nechceme za běhu měnit prototypy objektů, což přesně dělá Váš příklad ;-). Jinak nechci nijak tento způsob hájit, sám ho nepoužívám, ten ukázaný v článku je určitě v 99% případů lepší.
Ahoj,
velice mě potěšilo když jsem článek četl, včetně komentářů.
Chtěl bych se vás ale zeptat na jinou věc.
Netlačí mě ani tak dědičnost nebo ona praktická věc – netvořit třídy, které by měli „privátní“ proměnné (I. díl) a spíš než to bych správně používal prototype abych tyto proměnné či další metody mohl odkudkoli měnit apod…
Když něco v JS píši, spíš než tyto věci mi ve většině případů vyvstává potřeba mít objekt/třídu/fci, kterou bych mohl vytvořit bez toho, aniž bych dopředu věděl kolik jich bude a tyto mé instance se vždy dokázali postarat sami o sebe s tím co mají za hodnoty, bez ohledu na to co je v nejvyšším namespace, i co se týče postarání se o objekty v DOM, podle toho kteréžto CSS selectory jim byli předány k práci – příklad – rozbalovací horizontální menu (<del>http://www2.studentagency.cz/?node=1193</del>).
K věci – jak vytvořit způsobem, který tu byl pospán jako nejlepší cesta třídu, která by uměla řešit volání své vnitřní metody v intervalu? Se svými hodnotami proměných?
Článek k problému najdete třeba zde:
http://www.vonloesch.de/node/32
Jak to zapsat jinak (pomocí prototype) než pomocí způsobu z prvního článku, který je trochu add hoc řešení, ale přesto se v praxi pro tuto potřebu ukázal jako best way?
mluvím o tomto:
Díky, tf.
Z toho článku z vonloesch.de si příklad neberte. To je úplně špatný přístup ;)
Bez jakékoliv knihovny se to řeší takto:
Takže closure ano, ale jak vidíte, netvoříme tak celou „třídu“. Pouze se postaráme, aby jedna metoda dostala správný kontext. Pokud používáte nějaký framework, tak skoro každý implementuje curry metodu ‚bind‘:
Metoda process by pak vypadala takhle:
Správný framework pak nabízí vlastní metodu pro interval, s možností kontext určit:
Pane jo, vy jste to s tím kontextem vlastně vyřešil úplně elegantně:)
Příště budu snad už přemýšlet podobně.
Používám zatím jen jQuery, pomalu pročichávám Google Closure, ale jde to pomaleji než ten před tím. Každopádně díky moc za vaši dobrou radu.
A taky bych řek, že od teď už vlastně nemám potřebu psát kód v JS pomocí přístupů, které jste zmiňoval jako zlé.
Nakonec se mi tím otvírá daleko více možností.
Děkuji vám.
Kde vám můžu přidat karmu?
Pod komentářem je + a -.
:) jojo, záhy jsem je pak viděl.
Budu se tešit na další článek:-)
Možná by stálo zmínit, že v ES5 je metoda bind standardizovaná. Najdeme ji v prototypovém objektu Function. Tudíž bychom mohli psát v metodě process udělat něco takového:
Ano, jsem si vědom, že píši komentář téměř 2 roky po vydání článku :)
‚extends‘ je slovo rezervované pro případné použití v budoucnu a nelze jej tedy použít jako identifikátor (možná před těmi dvěma lety ještě nebylo, nevím, nejsem expert na historii JS) – viz třeba referenční příručku – https://developer.mozilla.org/en/JavaScript/Reference/Reserved_Words#Words_reserved_for_possible_future_use (na kterou je ostatně odkaz v prvním dílu seriálu)
Takže ani poslední příklad na jsFiddle nefunguje (asi původně fungoval, že si toho autor nevšiml).