Dědičnost objektů Javascript. Dědičnost v JavaScriptu. Zajištění funkčnosti konstruktorů základní třídy

JavaScript je pro vývojáře se zkušenostmi s jazyky založenými na třídách (jako Java nebo C++) trochu matoucí, protože je dynamický a neposkytuje implementaci třídy jako takovou (klíčové slovo class je zavedeno v ES2015, ale je to syntaktický cukr, JavaScript zůstává založen na prototypu).

Pokud jde o dědičnost, JavaScript má pouze jednu konstrukci: objekty. Každý objekt má soukromou vlastnost, která obsahuje odkaz na jiný objekt nazývaný jeho prototyp. Tento prototypový objekt má svůj vlastní prototyp a tak dále, dokud není dosaženo objektu s nulou jako prototypem. Podle definice nemá null žádný prototyp a funguje jako poslední článek tohoto prototypového řetězce.

Dědění "metod"

JavaScript ano nemít„metody“ ve formě, ve které je třídní jazyky definují. V JavaScriptu lze k objektu přidat jakoukoli funkci ve formě vlastnosti. Zděděná funkce funguje stejně jako jakákoli jiná vlastnost, včetně stínování vlastnosti, jak je ukázáno výše (v tomto případě forma přeřazení metody).

Když je provedena zděděná funkce, hodnota tohoto ukazuje na dědící objekt, nikoli na prototypový objekt, kde je funkce vlastní vlastností.

Var o = ( a: 2, m: function() ( return this.a + 1; ) ); console.log(o.m()); // 3 // Při volání o.m v tomto případě "toto" odkazuje na o var p = Object.create(o); // p je objekt, který dědí z o p.a = 4; // vytvoří vlastnost "a" na p console.log(p.m()); // 5 // když se řekne p.m, "toto" odkazuje na p. // Takže když p zdědí funkci m z o, // "this.a" znamená p.a, vlastnost "a" z p

Použití prototypů v JavaScriptu

Podívejme se na to, co se děje v zákulisí, trochu podrobněji.

V JavaScriptu, jak je uvedeno výše, mohou mít funkce vlastnosti. Všechny funkce mají speciální vlastnost s názvem prototype . Vezměte prosím na vědomí, že níže uvedený kód je samostatně stojící (je bezpečné předpokládat, že na webové stránce není žádný jiný JavaScript než níže uvedený kód). Pro nejlepší zážitek z učení se důrazně doporučuje otevřít konzoli (což lze v Chrome a Firefoxu provést stisknutím Ctrl+Shift+I), přejít na kartu „konzole“, zkopírovat a vložit do pod kódem JavaScript a spusťte jej stisknutím klávesy Enter/Return.

Funkce doSomething()() console.log(doSomething.prototype); // Nezáleží na tom, jak funkci deklarujete, // funkce v JavaScriptu bude mít vždy výchozí vlastnost // prototype. var doSomething = funkce())(; console.log(doSomething.prototype);

Jak je vidět výše, doSomething() má výchozí vlastnost prototypu, jak ukazuje konzole. Po spuštění tohoto kódu by konzole měla zobrazit objekt, který vypadá podobně jako tento.

( konstruktor: ƒ doSomething(), __proto__: ( konstruktor: ƒ Object(), hasOwnProperty: ƒ hasOwnProperty(), isPrototypeOf: ƒ isPrototypeOf(), propertyIsEnumerable: ƒ propertyIsEnumerable(), toLocaleLocal toString toString: ƒString (), valueOf: ƒ valueOf() ) )

K prototypu doSomething() můžeme přidat vlastnosti, jak je ukázáno níže.

Funkce doSomething()() doSomething.prototype.foo = "bar"; console.log(doSomething.prototype);

Výsledkem je:

( foo: "bar", konstruktor: ƒ doSomething(), __proto__: ( konstruktor: ƒ Object(), hasOwnProperty: ƒ hasOwnProperty(), isPrototypeOf: ƒ isPrototypeOf(), propertyIsEnumerable: ƒ to propertyIsEnumerable(LeLocal), toLocal ), toString: ƒ toString(), valueOf: ƒ valueOf() ) )

Nyní můžeme použít operátor new k vytvoření instance doSomething() založené na tomto prototypu. Chcete-li použít operátor new, jednoduše zavolejte funkci normálně, kromě předpony new . Volání funkce s operátorem new vrátí objekt, který je instancí funkce. K tomuto objektu lze poté přidat vlastnosti.

Zkuste následující kód:

Funkce doSomething()() doSomething.prototype.foo = "bar"; // přidání vlastnosti do prototypu var doSomeInstancing = new doSomething(); doSomeInstancing.prop = "nějaká hodnota"; // přidání vlastnosti do objektu console.log(doSomeInstancing);

Výsledkem je výstup podobný následujícímu:

( prop: "nějaká hodnota", __proto__: ( foo: "bar", konstruktor: ƒ doSomething(), __proto__: ( konstruktor: ƒ Object(), hasOwnProperty: ƒ hasOwnProperty(), isPrototypeOf: ƒ isPrototypeOf(), vlastnostIsEnumerable ƒ propertyIsEnumerable(), toLocaleString: ƒ toLocaleString(), toString: ƒ toString(), valueOf: ƒ valueOf() ) ) )

Jak je vidět výše, __proto__ doSomeInstancing je doSomething.prototype . Ale co to dělá? Když přistoupíte k vlastnosti doSomeInstancing , prohlížeč nejprve zkontroluje, zda doSomeInstancing tuto vlastnost má.

Pokud doSomeInstancing vlastnost nemá, prohlížeč hledá vlastnost v __proto__ doSomeInstancing (také znám jako doSomething.prototype). Pokud __proto__ doSomeInstancing hledaná vlastnost, pak se použije tato vlastnost na __proto__ doSomeInstancing.

V opačném případě, pokud __proto__ doSomeInstancing vlastnost nemá, pak se pro tuto vlastnost zkontroluje __proto__ __proto__ doSomeInstancing. Ve výchozím nastavení je vlastnost prototypu jakékoli funkce __proto__ window.Object.prototype . Takže se prohlédne __proto__ __proto__ doSomeInstancing (také znám jako __proto__ doSomething.prototype (také znám jako Object.prototype pro vlastnost)). je hledaný.

Pokud vlastnost není nalezena v __proto__ __proto__ doSomeInstancing, pak se prohledá __proto__ __proto__ __proto__ doSomeInstancing. Vyskytl se však problém: __proto__ __proto__ __proto__ doSomeInstancing neexistuje. Teprve poté, poté, co je prohlédnut celý prototypový řetězec __proto__ "s a již neexistují žádné __proto__ s, prohlížeč tvrdí, že vlastnost neexistuje a dojde k závěru, že hodnota vlastnosti není definována.

Zkusme do konzole zadat další kód:

Funkce doSomething()() doSomething.prototype.foo = "bar"; var doSomeInstancing = new doSomething(); doSomeInstancing.prop = "nějaká hodnota"; console.log("doSomeInstancing.prop: " + doSomeInstancing.prop); console.log("doSomeInstancing.foo: " + doSomeInstancing.foo); console.log("doSomething.prop: " + doSomething.prop); console.log("doSomething.foo: " + doSomething.foo); console.log("doSomething.prototype.prop: " + doSomething.prototype.prop); console.log("doSomething.prototype.foo: " + doSomething.prototype.foo);

Výsledkem je následující:

DoSomeInstancing.prop: nějaká hodnota doSomeInstancing.foo: bar doSomething.prop: nedefinovaný doSomething.foo: nedefinovaný doSomething.prototype.prop: nedefinovaný doSomething.prototype.foo: bar

Různé způsoby vytváření objektů a výsledný řetězec prototypů Objekty vytvořené pomocí konstrukcí syntaxe var o = (a: 1); // Nově vytvořený objekt o má Object.prototype jako [] // o nemá žádnou vlastní vlastnost s názvem "hasOwnProperty" // hasOwnProperty je vlastní vlastností Object.prototype. // Takže o zdědí hasOwnProperty z Object.prototype // Object.prototype má jako prototyp hodnotu null. // o ---> Object.prototype ---> null var b = ["yo", "whadup", "?"]; // Pole dědí z Array.prototype // (který má metody indexOf, forEach atd.) // Řetězec prototypu vypadá takto: // b ---> Array.prototype ---> Object.prototype ---> null function f() ( return 2; ) // Funkce dědí z Function.prototype // (která má metody volání, vazbu atd.) // f ---> Function.prototype ---> Object.prototype -- -> null S konstruktorem

"Konstruktor" v JavaScriptu je "jen" funkce, která se náhodou volá operátorem new .

Funkce Graph() ( this.vertices = ; this.edges = ; ) Graph.prototype = ( addVertex: function(v) ( this.vertices.push(v); ) ); var g = new Graph(); // g je objekt s vlastními vlastnostmi "vrcholy" a "hrany". // g.[] je hodnota Graph.prototype, když je spuštěn nový Graph().

S Object.create "použít přísné"; class Polygon ( konstruktor(výška, šířka) ( this.height = výška; this.width = šířka; ) ) class Square extends Polygon ( konstruktor(stranaDélka) ( super(délka strany, Délka strany); ) get area() ( return this. výška * this.width ) set sideLength(newLength) ( this.height = newLength; this.width = newLength; ) var square = new Square(2); Výkon

Doba vyhledávání vlastností, které jsou vysoko v řetězci prototypu, může mít negativní dopad na výkon, a to může být významné v kódu, kde je výkon kritický. Navíc pokus o přístup k neexistujícím vlastnostem bude vždy procházet celým řetězcem prototypu.

Také při iteraci přes vlastnosti objektu bude vyčíslena každá vyčíslitelná vlastnost, která je v řetězci prototypu. Chcete-li zkontrolovat, zda má objekt definovanou vlastnost sám a ne někde na jeho prototypovém řetězci, je nutné použít metodu hasOwnProperty, kterou všechny objekty dědí z Object.prototype . Abychom vám dali konkrétní příklad, ukažme si výše uvedený příklad kódu grafu, který jej ilustruje:

Console.log(g.hasOwnProperty("vertices")); // true console.log(g.hasOwnProperty("nope")); // false console.log(g.hasOwnProperty("addVertex")); // false console.log(g.__proto__.hasOwnProperty("addVertex")); // pravda

hasOwnProperty je jediná věc v JavaScriptu, která se zabývá vlastnostmi a neprochází řetězcem prototypů.

Poznámka: Nestačí zkontrolovat, zda vlastnost není definována . Vlastnost může velmi dobře existovat, ale její hodnota je náhodou nastavena na undefined .

Špatná praxe: Rozšíření nativních prototypů

Jednou z často používaných chyb je rozšíření Object.prototype nebo jednoho z dalších vestavěných prototypů.

Tato technika se nazývá opičí záplatování a přestávky zapouzdření. I když je používají populární frameworky, jako je Prototype.js, stále neexistuje dobrý důvod pro zahlcování vestavěných typů dalšími nestandardní funkčnost.

Jediným dobrým důvodem pro rozšíření vestavěného prototypu je backportování funkcí novějších enginů JavaScript, jako je Array.forEach .

Shrnutí metod pro rozšíření řetězce prototypů

Zde jsou všechny 4 způsoby a jejich výhody/proti. Všechny níže uvedené příklady vytvářejí přesně stejný výsledný objekt inst (tedy zaznamenávají stejné výsledky do konzoly), s výjimkou odlišných způsobů pro účely ilustrace.

Jméno Příklad(y) Pro(s) Nevýhody
Nová inicializace function foo()() foo.prototype = ( foo_prop: "foo val" ); function bar()() var proto = new foo; proto.bar_prop = "bar val"; bar.prototyp = proto; var inst = nová lišta; console.log(inst.foo_prop); console.log(inst.bar_prop); Podporováno v každém představitelném prohlížeči (podpora sahá až k IE 5.5!). Je také velmi rychlý, velmi standardní a velmi dobře optimalizovaný pro JIST. Aby bylo možné použít tuto metodu, musí být daná funkce inicializována. Během této inicializace může konstruktor ukládat jedinečné informace, které musí být vygenerovány pro každý objekt. Tyto jedinečné informace by však byly generovány pouze jednou, což by mohlo vést k problémům. Navíc inicializace konstruktoru může do objektu vložit nežádoucí metody. Obojí však obecně nejsou vůbec problémy (ve skutečnosti obvykle prospěšné), pokud je to celý váš vlastní kód a víte, co kde co dělá.
Object.create function foo()() foo.prototype = ( foo_prop: "foo val" ); function bar()() var proto = Object.create(foo.prototype); proto.bar_prop = "bar val"; bar.prototyp = proto; var inst = nová lišta; console.log(inst.foo_prop); console.log(inst.bar_prop); function foo()() foo.prototype = ( foo_prop: "foo val" ); function bar()() var proto = Object.create(foo.prototype, ( bar_prop: ( hodnota: "bar val" ) )); bar.prototyp = proto; var inst = nová lišta; console.log(inst.foo_prop); console.log(inst.bar_prop) Podpora ve všech dnes používaných prohlížečích, což jsou všechny prohlížeče jiných výrobců než Microsoft plus IE9 a vyšší. Umožňuje přímé nastavení __proto__ způsobem, který je pouze jednorázový, takže prohlížeč může objekt lépe optimalizovat. Umožňuje také vytváření objektů bez prototypu pomocí Object.create(null) .

Není podporováno v IE8 a nižších. Protože však společnost Microsoft ukončila rozšířenou podporu pro systémy s těmito starými prohlížeči, nemělo by to být pro většinu aplikací problémem. Navíc pomalá inicializace objektu může být výkonová černá díra, pokud použijete druhý argument, protože každá vlastnost deskriptoru objektu má svůj vlastní samostatný objekt deskriptoru. Při práci se stovkami tisíc deskriptorů objektů ve formě objektu může se zpožděním nastat vážný problém.

Object.setPrototypeOf function foo()() foo.prototype = ( foo_prop: "foo val" ); function bar()() var proto = ( bar_prop: "bar val" ); Object.setPrototypeOf(proto, foo.prototype); bar.prototyp = proto; var inst = nová lišta; console.log(inst.foo_prop); console.log(inst.bar_prop); Měl by být podceňovaný a nevýkonný. Urychlení běhu Javascriptu je zcela vyloučeno, pokud se to odvážíte použít ve finálním produkčním kódu, protože mnoho prohlížečů optimalizuje prototyp a snaží se uhodnout umístění metody v paměti při volání instance předem, ale nastavení prototypu dynamicky narušuje všechny tyto optimalizace a může dokonce donutit některé prohlížeče, aby znovu zkompilovaly váš kód pro deoptimalizaci, jen aby fungoval podle specifikací. Není podporováno v IE8 a nižších.
__proto__ function foo()() foo.prototype = ( foo_prop: "foo val" ); function bar()() var proto = ( bar_prop: "bar val", __proto__: foo.prototype ); bar.prototyp = proto; var inst = nová lišta; console.log(inst.foo_prop); console.log(inst.bar_prop); var inst = ( __proto__: ( bar_prop: "bar val", __proto__: ( foo_prop: "foo val", __proto__: Object.prototype ) ) ); console.log(inst.foo_prop); console.log(inst.bar_prop) Podpora ve všech dnes používaných prohlížečích, což jsou všechny prohlížeče jiných výrobců než Microsoft plus IE11 a vyšší. Nastavení __proto__ na něco, co není objekt, selže pouze tiše. Nevyvolá výjimku.
Hrubě zastaralé a nevýkonné. Urychlení běhu Javascriptu je zcela vyloučeno, pokud se to odvážíte použít ve finálním produkčním kódu, protože mnoho prohlížečů optimalizuje prototyp a snaží se uhodnout umístění metody v paměti při volání instance předem, ale nastavení prototypu dynamicky narušuje všechny tyto optimalizace a může dokonce donutit některé prohlížeče, aby znovu zkompilovaly váš kód pro deoptimalizaci, jen aby fungoval podle specifikací. Není podporováno v IE10 a nižších.

prototyp a Object.getPrototypeOf

Pravděpodobně jste si již všimli, že naše funkce A má speciální vlastnost zvanou prototyp . Tato speciální vlastnost funguje s operátorem JavaScript new. Odkaz na objekt prototypu se zkopíruje do vnitřní vlastnosti [] nové instance. Například, když uděláte var a1 = new A() , JavaScript (po vytvoření objektu v paměti a před spuštěním funkce A() s tímto definovaným) nastaví a1.[] = A.prototype . Když potom přistoupíte k vlastnostem instance, JavaScript nejprve zkontroluje, zda existují přímo na daném objektu, a pokud ne, vyhledá v [] . To znamená, že všechny věci, které definujete v prototypu, jsou efektivně sdíleny všemi instancemi a můžete dokonce později změnit části prototypu a nechat se změny objevit ve všech existujících instancích, pokud chcete.

Pokud ve výše uvedeném příkladu uděláte var a1 = new A(); var a2 = new A(); pak a1.doSomething by ve skutečnosti odkazovalo na Object.getPrototypeOf(a1).doSomething , což je stejné jako A.prototype.doSomething, které jste definovali, tj. Object.getPrototypeOf(a1).doSomething == Object.getPrototypeOf(a2).doSomething == A.prototype.doSomething .

Stručně řečeno, prototyp je pro typy, zatímco Object.getPrototypeOf() je stejný pro instance.

[] se dívá na rekurzivně, tj. a1.doSomething , Object.getPrototypeOf(a1).doSomething , Object.getPrototypeOf(Object.getPrototypeOf(a1)).doSomething atd., dokud nebude nalezen nebo Object.getPrototypeOf nevrátí hodnotu null.

Takže když zavoláš

Var o = new Foo();

JavaScript vlastně prostě dělá

Var o = new Object(); o.[] = Foo.prototype; Foo.call(o);

(nebo něco takového) a když to uděláte později

O.someProp;

kontroluje, zda o má vlastnost someProp . Pokud ne, zkontroluje Object.getPrototypeOf(o).someProp , a pokud neexistuje, zkontroluje Object.getPrototypeOf(Object.getPrototypeOf(o)).someProp a tak dále.

Na závěr

Před napsáním složitého kódu, který jej využívá, je nezbytné porozumět prototypovému modelu dědičnosti. Uvědomte si také délku prototypových řetězců ve vašem kódu a v případě potřeby je rozdělte, abyste se vyhnuli možným problémům s výkonem. Dále by nativní prototypy nikdy neměly být rozšiřovány, pokud to není z důvodu kompatibility s novějšími funkcemi JavaScriptu.

Není nic trvalejšího než dočasné.
Lidová moudrost

Pokud si vzpomínáte, předchozí diskuze skončila před půl rokem tím, že při programování v JavaScriptu je velmi dobré používat prototypy objektů. Nyní je čas tento pojem objasnit a zároveň ukázat, jak jej používat ještě efektivněji.

V JavaScriptu může mít každý objekt asociaci s jiným objektem – tzv. „prototypem“. Pokud hledání určité vlastnosti (nebo metody - to je totéž) ve zdrojovém objektu skončí neúspěšně, interpret se pokusí najít stejnojmennou vlastnost (metodu) v jeho prototypu, poté v prototypu prototypu atd. Pokud jsme například požadovali odkaz na obj.prop (nebo, což je přesně totéž, obj["prop")], JavaScript začne hledat vlastnost prop v samotném objektu obj, pak v prototypu obj, prototyp prototypu obj a tak dále až do konce.

Tajemství prototypů

Na internetu je spousta literatury popisující, co je prototyp a v jakém kontextu se obvykle používá. Lví podíl článků však trpí jednou věcí velká nevýhoda: Není to tam podrobně vysvětleno, jak přesně prototypy fungují, když Může uplatnit a kdy - je to zakázáno.

Ukažme si „klasické“ použití prototypů pro implementaci dědičnosti v JavaScriptu.

Výpis 1

//** //** Auto základní "třídy".

//** funkce Car() ( document.writeln("Byl zavolán konstruktor Car()."); ) // Definuje novou metodu "třídy" Car.

Car.prototype.drive = function() ( document.writeln("Called Car.drive()"); ) //** //** Odvozené "třídy" Zaporojets (Zaporožec je také Auto).

//** funkce Zaporojets() ( document.writeln("Byl zavolán konstruktor Zaporojets()."); ) // Říkáme, že prototyp Car je "třída" Zaporojets.

Zaporojets.prototype = new Car(); // Definujte novou metodu "třídy" Zaporojets. Zaporojets.prototype.crack = function() ( document.writeln("Called Zaporojets.crack()"); ) //** //** Hlavní program.//** document.writeln("Program byl spuštěn."); // Vytvořte objekt odvozené "třídy" Zaporojets. // (*) funkce základního objektu se nazývá var other = new Zaporojets(); vozidlo.crack(); derivační konstruktory. V JavaScriptu, jak již bylo řečeno v předchozím tutoriálu, neexistují žádné třídy, existují pouze objekty. Zde vidíme úplně jiný obrázek: Konstruktér Car dokonce začal na zobrazí hlášení "Program se spustil"! Navíc, když byl objekt Zaporojets znovu vytvořen, nebyl zavolán konstruktér Car, což znamená stejný objekt Car je „sdílený“ mnoha objekty Zaporojets! Z hlediska ideologie dědictví je to zcela špatně.

Bohužel je nemožné nastavit prototyp pro objekt bez předchozího vytvoření objekt základní třída. Pokud chcete přiřadit Zaporojets.prototype novou hodnotu, jednoduše povinný použijte nový operátor Car(). Jinými slovy, vytvoření podobjektu základní „třídy“ se provádí v JavaScriptu Ne v konstruktoru derivátu (jako ve všech ostatních objektově orientovaných jazycích) a mnohem dříve, dokonce i ve fázi konstrukce „třídy potomků“, a to pouze jednou.

Toto chování samozřejmě vyplývá ze způsobu napsání programu. Ve skutečnosti jsme objekt Car vytvořili pouze jednou - při přiřazení hodnoty prototypu Zaporojets; podle toho byl jeho konstruktor v tuto chvíli volán pouze jednou.

Závěr: v JavaScriptu je „standardní“ dědičnost implementována zcela jinak než v jiných „třídě orientovaných“ programovacích jazycích. Pojem "konstruktor" v něm není stejný jako konstruktor v C++, Javě nebo dokonce v Perlu.

Jak nejsou prototypy?

Stejně jako v zenu, abychom lépe porozuměli tomu, co je pojem, je někdy užitečné porozumět tomu, co přesně to je není. Ve třicáté deváté knize bylo řečeno, že každý objekt (nebo, co je totéž, hash) může mít svůj vlastní prototyp hash, který je zobrazen interpretem, pokud některá vlastnost aktuálního objektu chybí. Na základě toho jste mohli s velkou radostí okamžitě spěchat napsat něco jako následující kód:

Výpis 3

Var obj = ( // Ale má prototyp... prototyp: ( // ...ve kterém tuto vlastnost je definováno... prop: 101 ) // ...takže interpret to nakonec musí přečíst.

) // Zkontrolujeme? alert("Hodnota vlastnosti: " + obj.prop); // Jaký... Běda a ah: tento příklad

nefunguje

, výstup: "Hodnota vlastnosti: nedefinováno." Přiřazení nové hodnoty vlastnosti prototypu libovolného objektu nám tedy nic nedává!

Var obj = ( // V samotném objektu není žádná vlastnost prop. ) // Zkusme přistupovat k prototypu jiným způsobem.

obj.constructor.prototype.prop = 101; // Zkontrolujeme? alert("Hodnota vlastnosti: " + obj.prop); // V tomto objektu vlastnosti by to nemělo být... ​​var newObj = (); // prázdný hash alert("Empty: " + newObj.prop); // Odkud to je? Výsledek "Hodnota vlastnosti: 101" nám říká, že program fungoval. Nicméně, za jakou cenu? Nyní se objevila vlastnost rekvizity

obecně v jakékoli

  • objekt vytvořený kdykoli v programu, nejen v obj! To lze ověřit druhým voláním alert() , které hrdě oznamuje, že „prázdnota“ se ukazuje jako číslo 101. („Osvěťte se a všechny bytosti na světě budou osvícené.“) Jaké závěry lze z příkladu vyvodit? V
  • samotný objekt
  • vlastnost prototype nemá žádný zvláštní význam. Ne Prototyp objektu by měl být přístupný prostřednictvím vlastnosti nástroje konstruktoru přítomné v jakémkoli hash.
  • Výraz obj.constructor.prototype (a

    obj.prototyp ! to je důležité!) znamená prototyp objektu.

    Nový operátor a obj.constructor

    Nový objekt v JavaScriptu lze vytvořit pouze jedním způsobem: pomocí operátoru new:

    Výpis 5

    Var vehicle = new Car(); // vytvoření nového objektu var hash = (); // zkratka pro new Object() var array = ; // zkratka pro new Array()

    Jen málo lidí o tom přemýšlí, ale první příkaz v příkladu je zcela ekvivalentní tomuto kódu:

    Výpis 6

    Var vozidlo = nové okno.Auto(); // můžete to udělat... var vehicle = new self.Car(); // v prohlížeči self==window

    nebo dokonce něco takového:

    Výpis 7

    Funkčně se také neliší od následujícího příkladu:

    Výpis 8

    // Vytvořte objekt standardním způsobem.

    self.Car = function() ( alert("Car") ) var vehicle = new self.Car();

    Tak co, líbilo se ti to? Začínáte si všímat vzorců? Zde jsou další příklady: Výpis 9// Vytvořte "třídu" za běhu.

    Po vytvoření objektu tedy interpret přiřadí jeho vlastnosti konstruktoru hodnotu rovnou hodnotě napravo od nového operátoru. Vehicle.constructor == self.Car a obj.constructor v posledním příkladu obecně odkazují na funkci, která nemá samostatný název v globálním rozsahu (anonymní). To je tak důležité, že pro objasnění uvedu další příklad:

    Výpis 10

    // Vytvořte "třídu" za běhu.

    var clazz = function() ( alert("Dynamic!") ) var obj = new clazz(); alert(obj.constructor == clazz); // vypíše true!

    Ale promiňte, vždyť napravo od nového nemůže stát absolutně nic. Například je zde neplatné číslo nebo řetězec... Následující příklad také nefunguje:

    Výpis 11

    Var clazz = (); // clazz.constructor == self.Object var obj = new clazz(); // nefunguje! Co můžete u nového operátora využít? Odpověď je jednoduchá: pouze funkce

    (přesněji řečeno objekty, jejichž konstruktorem je self.Function). A abychom byli ještě přesnější, je povoleno používat standardní JavaScriptové objekty self.Array , self.String atd.

    Ukazuje se, že pouze objekty, které lze použít na pravé straně nového, mají vlastnost prototypu se speciálním účelem! Platná jsou například volání Function.prototype , String.prototype nebo Array.prototype.

    Nyní chápete, proč JavaScript nepovažuje prvek obj.prototype libovolného hash objektu za speciální, ale odkazuje na obj.constructor.prototype ? Prototyp má speciální účel pouze pro vestavěný objekt, kterým je vždy odkaz obj.constructor.

    Takže závěr: k prototypům objektů se přistupuje prostřednictvím řetězce obj.constructor.prototype.constructor.prototype... , a nikoli obj.prototype.prototype , jak lze pochopit z mnoha JavaScriptových tutoriálů na internetu. Konstruktor objektu může být pouze objektem vestavěné třídy (obvykle Funkce).

    Zajištění funkčnosti konstruktorů základní třídy

    Tato nabla má cyklickou povahu a nyní, když dobře rozumíme tomu, jak prototypy a návrháři fungují, se opět vracíme k úplně prvnímu příkladu. Budeme hovořit o vytváření základních a odvozených objektů ve stylu „třídě orientovaného“ programování.

    • Stojíme tedy před následujícími úkoly:
    • Vynutit volání konstruktérů základních objektů při vytváření derivátů.

    Pokud programujete v „čistém“ JavaScriptu, výsledkem těchto dvou úloh je poněkud těžkopádný kód. Abyste to nepsali pokaždé, doporučuji vám použít velmi malou knihovnu, která poskytuje pohodlné použití příslušných přístupů. S jeho pomocí vypadá vytváření odvozených tříd velmi jednoduše:

    Výpis 12

    // Základní "třída".

    Auto = newClass(null, ( konstruktor: function() ( document.writeln("Zavolán konstruktor Car()."); ), drive: function() ( document.writeln("Car.drive() volá"); ) )); // Odvozená "třída".

    Zaporojets = newClass(Car, ( konstruktor: function() ( document.writeln("Byl zavolán konstruktor Zaporojets()."); this.constructor.prototype.constructor.call(this); ), crack: function() ( document. writeln("Zavoláno Zaporojets.crack()"); drive: function() ( document.writeln("Zavoláno Zaporojets.drive()"); vrátit this.constructor.prototype.drive.call(this) ))); document.writeln("Program byl spuštěn."); // Vytvoří objekt odvozené "třídy".

    var vehicle = new Zaporojets(); vozidlo.drive(); // je volána funkce základního objektu // Vytvořte další objekt stejné třídy.

    var vehicle = new Zaporojets(); vozidlo.crack(); // funkce odvozeného objektu Výsledek tohoto příkladu se radikálně liší od toho, co bylo uvedeno na začátku experimentu. Výpis 13

    Program byl zahájen. Zavolá se konstruktor Zaporojets(). Zavolá se konstruktor Car(). Volal Zaporojets.drive() Volal Car.drive() Volal konstruktor Zaporojets(). Zavolá se konstruktor Car(). S názvem Zaporojets.crack()

    Jak vidíte, vše funguje tak, jak by programátor v „třídně orientovaném“ jazyce očekával: konstruktor Car() se volá spolu s konstruktorem Zaporojets(). Musíte však spustit konstruktor základní třídy v konstruktoru odvozené třídy

    samozřejmě (současně je ukázáno, jak volat metodu jednotky ze základního objektu, pokud byla přepsána v odvozeném objektu):). Mohu říci, že tyto informace by měly být zcela dostatečné.

    Výpis 15

    // // Vytvoří správně odvozenou "třídu".
    • // // Verze: 1.2 // function newClass(parent, prop) ( // Dynamicky vytvořte konstruktor třídy. var clazz = function() ( // Hloupý JS potřebuje přesně jeden "operátor nový" volání nadřazeného // konstruktoru hned po definice třídy if (clazz.preparing) return delete(clazz.preparing) // Volání vlastního konstruktoru if (clazz.constr) ( this.constructor = clazz; // potřebujeme to! clazz.constr.apply(this,); argumenty ) ) clazz.prototype = (); // standardně žádný prototyp if (rodič) ( parent.preparing = true; clazz.prototype = nový rodič; clazz.prototype.constructor = rodič; clazz.constr = rodič; // VÝCHOZÍ - rodičovský konstruktor ) if (prop) ( var cname = "konstruktor"; for (var k v prop) ( if (k != cname) clazz.prototype[k] = prop[k]; ) if ( prop && prop != Object) clazz.constr = prop ) return clazz )

    Překlad

    Poznámka překladatele: Téma dědičnosti v JavaScriptu je pro začátečníky jedno z nejobtížnějších. S přidáním nové syntaxe s klíčovým slovem class se porozumění dědičnosti zjevně nezlepšilo, i když se neobjevilo nic radikálně nového. Tento článek se nedotýká nuancí implementace prototypové dědičnosti v JavaScriptu, takže pokud má čtenář nějaké dotazy, doporučuji přečíst si následující články: Základy a mylné představy o JavaScriptu a Porozumění OOP v JavaScriptu [1. část]

    V případě jakýchkoli připomínek souvisejících s překladem nás prosím kontaktujte do osobní zprávy.

    JavaScript je velmi výkonný jazyk. Tak silný, že v něm koexistuje mnoho různých způsobů navrhování a vytváření objektů. Každá metoda má své pro a proti a rád bych začátečníkům pomohl na to přijít. Toto je pokračování mého předchozího příspěvku, Stop "kategorizaci" JavaScriptu. Dostal jsem mnoho dotazů a komentářů s žádostí o příklady a právě za tímto účelem jsem se rozhodl napsat tento článek. JavaScript používá prototypickou dědičnost To znamená, že v JavaScript objekty jsou zděděny z jiných objektů. Jednoduché objekty v JavaScriptu vytvořené pomocí () složených závorek mají pouze jeden prototyp:. jsou zděděny z jiných objektů. Jednoduché objekty v JavaScriptu vytvořené pomocí () složených závorek mají pouze jeden prototyp: Objekt.prototyp jsou zděděny z jiných objektů. Jednoduché objekty v JavaScriptu vytvořené pomocí () složených závorek mají pouze jeden prototyp:, je zase také objekt a všechny vlastnosti a metody

    k dispozici pro všechny objekty. jsou zděděny z jiných objektů. Jednoduché objekty v JavaScriptu vytvořené pomocí () složených závorek mají pouze jeden prototyp: Pole vytvořené pomocí hranatých závorek mají několik prototypů, včetně A Pole.prototyp jsou zděděny z jiných objektů. Jednoduché objekty v JavaScriptu vytvořené pomocí () složených závorek mají pouze jeden prototyp: Pole vytvořené pomocí hranatých závorek mají několik prototypů, včetně A k dispozici pro všechna pole. Například vlastnosti a metody stejného jména .valueOf Pole vytvořené pomocí hranatých závorek mají několik prototypů, včetně .ToString, jsou volány z nejbližšího prototypu, v tomto případě z A.

    Definice prototypů a vytváření objektů Metoda 1: Vzor konstruktoru JavaScript má speciální typ funkce nazývané konstruktory, které fungují stejným způsobem jako konstruktory v jiných jazycích. Funkce konstruktoru se volají pouze pomocí klíčového slova nový a pomocí klíčového slova přidružit vytvořený objekt ke kontextu funkce konstruktoru tento. Typický konstruktor může vypadat takto:
    function Animal(type)( this.type = type; ) Animal.isAnimal = function(obj, type)( if(!Animal.prototype.isPrototypeOf(obj))( return false; ) return type ? obj.type === typ: true); function Pes(jméno, plemeno)( Zvíře.volání(toto, "pes"); toto.jméno = jméno; toto.plemeno = plemeno; ) Object.setPrototypeOf(Pes.prototyp, Zvíře.prototyp); Dog.prototype.bark = function())( console.log("ruff, ruff"); ); Dog.prototype.print = function())( console.log("Pes " + toto.jméno + " je " + toto.plemeno); ); Pes.jePes = function(obj)( return Animal.isAnimal(obj, "pes"); );
    Použití tohoto konstruktoru vypadá stejně jako vytvoření objektu v jiných jazycích:
    var sparkie = nový pes("Sparkie", "Border kolie"); sparkie.name; // "Sparkie" sparkie.breed; // "Border kolie" sparkie.bark(); // konzole: "ruff, ruff" sparkie.print(); // konzole: "Pes Sparkie je Border kolie" Dog.isDog(sparkie); // pravda
    kůra Pole vytvořené pomocí hranatých závorek mají několik prototypů, včetně vytisknout prototypové metody, které se vztahují na všechny objekty vytvořené pomocí konstruktoru Pes. Vlastnosti jméno Pole vytvořené pomocí hranatých závorek mají několik prototypů, včetně plemeno jsou inicializovány v konstruktoru. Je běžnou praxí, že všechny metody jsou definovány v prototypu a vlastnosti jsou inicializovány konstruktorem Metoda 2: Definování třídy v klíčovém slově ES2015 (ES6). třída byl od samého začátku rezervován v JavaScriptu a nyní je konečně čas jej použít. Definice tříd v JavaScriptu jsou podobné jako v jiných jazycích.
    class Animal ( konstruktor(typ)( this.type = typ; ) static isAnimal(obj, type)( if(!Animal.prototype.isPrototypeOf(obj))( return false; ) return type ? obj.type === type : true; ) ) třída Pes rozšiřuje Zvíře ( konstruktor(jméno, plemeno)( super("pes"); this.name = jméno; this.breed = plemeno; ) bark())( console.log("ruff, ruff" " ); ) print() ( console.log("Pes " + toto.jméno + " je " + toto.plemeno); ) static isPes(obj)( return Animal.isAnimal(obj, "pes"); ))
    Mnoho lidí považuje tuto syntaxi za vhodnou, protože kombinuje konstruktor a deklaraci statických a prototypových metod v jednom bloku. Použití je úplně stejné jako u předchozí metody.
    var sparkie = nový pes("Sparkie", "Border kolie"); třída Metoda 3: Explicitní deklarace prototypu, Object.create, tovární metoda Tato metoda ukazuje, jaká je ve skutečnosti nová syntaxe klíčového slova nový.
    používá prototypickou dědičnost. Tato metoda také umožňuje vytvořit nový objekt bez použití operátoru
    var Animal = ( create(type)( var animal = Object.create(Animal.prototype); animal.type = type; return animal; ), isAnimal(obj, type)( if(!Animal.prototype.isPrototypeOf(obj) )( return false; ) return type ? obj.type === typ: true ), prototyp: () ); var Pes = ( create(jméno, plemeno)( var proto = Object.assign(Animal.create("pes"), Dog.prototype); var dog = Object.create(proto); dog.name = jméno; pes. plemeno = plemeno vrátit psa ), isPes(obj)( return Animal.isAnimal(obj, "pes"); ), prototyp: ( bark())( console.log("ruff, ruff"); ), tisknout; ( )( console.log("Pes " + toto.jméno + " je " + toto.plemeno); ) ) ); Object.create Tato syntaxe je vhodná, protože prototyp je deklarován explicitně. Je jasné, co je definováno v prototypu a co je definováno v objektu samotném. Metoda je pohodlné, protože umožňuje vytvořit objekt ze zadaného prototypu. Kontrola s.isPrototypeOf
    v obou případech stále funguje. Použití jsou různá, ale ne přehnaná: Object.create.
    function Animal(type)( var animal = Object.create(Animal.prototype); animal.type = type; return animal; ) Animal.isAnimal = function(obj, type)( if(!Animal.prototype.isPrototypeOf(obj) )( return false; ) return type ? obj.type === typ: true ); Zvíře.prototyp = (); function Dog(jméno, plemeno)( var proto = Object.assign(Zvíře("pes"), Pes.prototyp); var pes = Object.create(proto); dog.name = jméno; pes.plemeno = plemeno; návrat pes ) Pes.jePes = function(obj)( return Animal.isAnimal(obj, "pes"); ); Dog.prototype = ( bark())( console.log("ruff, ruff"); ), print())( console.log("Pes " + this.name + " is a " + this.breed) ));
    Tato metoda je zajímavá, protože je podobná první metodě, ale nevyžaduje klíčové slovo nový a spolupracuje s operátorem instanceOf. Použití je stejné jako v první metodě, ale bez použití klíčového slova nový:
    var sparkie = Pes("Sparkie", "Border kolie"); sparkie.name; // "Sparkie" sparkie.breed; // "Border kolie" sparkie.bark(); // konzole: "ruff, ruff" sparkie.print(); // console: "Pes Sparkie je Border kolie" Dog.isDog(sparkie); // true Srovnávací metoda 1 vs. metoda 4 Existuje jen velmi málo důvodů, proč použít metodu 1 místo metody 4. Metoda 1 vyžaduje buď použití klíčového slova nový, nebo přidáním následující kontroly do konstruktoru:
    if(!(this instanceof Foo))( return new Foo(a, b, c); )
    V tomto případě je použití jednodušší Object.create tovární metodou. Nelze také používat funkce Funkce#volání nebo Funkce#použít s funkcemi konstruktoru, protože přepisují kontext klíčového slova tento. Výše uvedená kontrola může tento problém vyřešit, ale pokud potřebujete pracovat s neznámým počtem argumentů, měli byste použít tovární metodu 2 vs. metodu 3 Stejné uvažování o konstruktorech a operátoru nový výše uvedené platí i v tomto případě. Kontrola s instanceof vyžadováno, pokud je použita nová syntaxe třída bez použití operátora nový nebo se používají Funkce#volání nebo Funkce#použít.Můj názor Programátor by se měl snažit o srozumitelnost svého kódu. Syntaxe Metody 3 velmi jasně ukazuje, co se vlastně děje. Usnadňuje také použití vícenásobné dědičnosti a dědičnosti zásobníku. Od operátora nový porušuje princip otevřeno/zavřeno kvůli nekompatibilitě s uplatnit nebo volání, je třeba se tomu vyhnout. Klíčové slovo třída skrývá prototypickou povahu dědičnosti v JavaScriptu za maskou systému tříd.
    „Jednoduché je lepší než sofistikované“ a používání tříd, protože jsou považovány za „sofistikovanější“, je jen zbytečná technická bolest hlavy.
    Používání Object.create je výraznější a jasnější než použití spony nový Pole vytvořené pomocí hranatých závorek mají několik prototypů, včetně tento. Prototyp je navíc uložen v objektu, který může být mimo kontext samotné továrny, a lze jej tedy snadněji upravit a rozšířit přidáním metod. Stejně jako třídy v ES6.
    Klíčové slovo třída, může být nejškodlivější funkcí JavaScriptu. Nesmírně si vážím brilantních a velmi pracovitých lidí, kteří byli zapojeni do procesu psaní standardu, ale i brilantní lidé někdy dělají špatnou věc. -Eric Elliott
    Přidání něčeho zbytečného a možná škodlivého, v rozporu se samotnou povahou jazyka, je bezmyšlenkovité a chybné.
    Pokud se rozhodnete použít třída, upřímně doufám, že s vaším kódem nikdy nebudu muset pracovat. Podle mého názoru by se vývojáři měli vyhnout používání konstruktorů, třída Pole vytvořené pomocí hranatých závorek mají několik prototypů, včetně nový a používat metody, které jsou přirozenější pro paradigma a architekturu jazyka. Glosář Object.assign(a, b) zkopíruje všechny vyčíslitelné vlastnosti objektu b namítat A a poté objekt vrátí A
    Object.create(proto) vytvoří nový objekt ze zadaného prototypu proto
    Object.setPrototypeOf(obj, proto) mění vnitřní vlastnosti [] objekt obj na proto

    Štítky: Přidat štítky

    (prototyp). V každém případě, protože mluvíme o objektově orientovaném programování, je dědičnost o vytváření soubor objektů, které mají společné vlastnosti, na základě již existující.

    Podívejme se na příklad v nějakém fiktivním programovacím jazyce překvapivě podobném JavaScriptu:

    // Nejjednodušší způsob, jak vytvořit objekt var greeterInstance = ( osoba: null, pozdrav: function() ( return "Ahoj" + this.person; ) ); greeterInstance.person = "Alice"; // Nějaký kód, který používá greeterInstance ... // A tady potřebujeme podobný objekt, ale pro Boba var greeterInstance2 = ( person: null, pozdrav: function() ( return "Ahoj " + this.person; ) ); greeterInstance2.person = "Bob"; // ...

    Jejda, vypadá to jako kopírování/vkládání! Takový kód bude obtížné udržovat, protože změny budou muset být provedeny okamžitě na všech místech, kde je instance vytvořena. Pokusme se zlepšit:

    Function createGreeter(person) ( return ( osoba: osoba, pozdrav: function() ( return "Ahoj " + this.person; ) ); ) var aliceGreeter = createGreeter("Alice"); // Nějaký kód, který používá aliceGreeter ... var bobGreeter = createGreeter("Bob"); //...

    Skvělé, teď můžeme tvořit mnoho podobných objektů pomocí createGreeter() a zbavili jsme se duplikace kódu. Je to již dědictví? Ne, protože nikdo po nikom nic nedědí. Toto je způsob, jak znovu použít kód - ano.

    Funkce createGreeter(person) ( return ( person: person, pozdrav: function() ( return "Ahoj " + this.person; ) ); ) function createGateKeeper(person) ( var keeper = ( otevřeno: false, open: function() ( this.opened = true; console.log(this.greeting()); ) ) var greeter = createGreeter(person for (var k in keeper) ( greeter[k] = keeper[k]; ) return greeter ; ) var gateKeeper = createGateKeeper("Alice"); gateKeeper.open();

    Ale to je spíše jako dědičnost, protože pomocí createGateKeeper() můžeme vytvářet mnoho podobných objektů, z nichž každý je založena na předměty jako Greeter.

    Třídní dědičnost

    Zvažte analogii v reálném světě. Třídu lze vnímat jako plán, podle kterého se v továrně vytvářejí výrobky (předměty). Nákres produktu! = samotný produkt. To je jen informace o tom, jak postavit produkt (vytvořit objekt). V třídy založené dědictví, některé kresby dědí obecné vlastnosti z jiných výkresů. A pak objekty vytvořené na základě takových výkresů mají vlastnosti definované jak v prvním, tak ve druhém. Podívejme se na příklad v Pythonu 3:

    Class Greeter: def __init__(self, person): self.person = person def pozdrav(self): return "Ahoj " + self.person aliceGreeter = Greeter("Alice") # V mnoha jazycích (Java, C++, C# , atd ) je obvyklé psát aliceGreeter = new Greeter(...). # Věnujte pozornost novému klíčovému slovu. Tato implementace dědičnosti # se běžně nazývá „klasická“. print(aliceGreeter.greeting()) # „Návrh“ pro strážce brány je založen na plánu pro vítače. # To je zdědí všechny jeho vlastnosti. A také přidává ten svůj. class GateKeeper(Greeter): def __init__(self, person): super().__init__(person) self.opened = False def open(self): self.opened = True print(self.greeting()) gateKeeper = GateKeeper( "Alice") gateKeeper.open()

    Prototypová dědičnost

    V prototypické dědičnosti neexistuje žádný koncept plánu (třídy). Zde spíše mluvíme o nějakém prvotním vzorovém objektu. Tento objekt se používá k vytvoření mnoha dalších identických objektů, které jej rozšiřují další vlastnosti. Tito. je to jako v továrně, kde nejsou žádné výkresy výrobku, který se má vyrobit, ale existuje jediný vzorek. Úkolem inženýrů v takovém závodě je naučit se na základě vzorku reprodukovat kopie takového produktu a zabudovat do nich nové funkce. A ano, samotný produkt č. 0 je také plnohodnotným produktem (pamatujte na tu třídu! = předmět této třídy).

    Prototypová dědičnost může být implementována alespoň dvěma různými způsoby:

    • kopírování všech vlastností hlavního objektu do toho, který vzniká ve fázi jeho konstrukce
    • delegování volání na vlastnosti neuvedené v vytvořený objekt, základní objekt

    Oba způsoby mají svá pro a proti. Uvažujme implementace první metody:

    Funkce createBaseObject() ( return ( foo: "bar", metoda1: function() ( return 42; ) ); ) var base = createBaseObject(); function createChildObject() ( var child = ( baz: 9001, metoda2: function() ( return 43; ) ); for (var k in base) ( child[k] = base[k]; ) return child; )

    • Protože každý podřízený objekt obsahuje kopii základních vlastností, je vyžadována další paměť.
    • Další čas na kopírování základních vlastností do potomka během vytváření.
    • Rychlost přístupu k podřízeným vlastnostem netrpí delegováním (viz níže).
    • Změna základny objektu po vytvoření potomků neovlivní již vytvořené objekty (to může být plus i mínus).

    Uvažujme provádění na základě delegování(Upozorňujeme, že se nejedná o JavaScript, ale o nějaký fiktivní jazyk, který je mu překvapivě podobný):

    Funkce createBaseObject() ( return ( foo: "bar", metoda1: function() ( return 42; ) ); ) var base = createBaseObject(); function createChildObject() ( return ( baz: 9001, metoda2: function() ( return 43; ), __get__: function(prop) ( claim !this.hasOwnProperty(prop) return base; // Delegování volání na základní objekt ) )

    Předpokládejme, že metoda „magic“ __get__ přepíše chování při přístupu k vlastnostem, které nejsou nastaveny na samotném objektu. Tito. uvnitř __get__ volání this.hasOwnProperty(prop) vždy vrátí false .

    • Rychlost takového kódu by měla být nižší (s výjimkou různých optimalizací pod kapotou jazyka) kvůli dodatečné úrovni nepřímosti zavedené metodou __get__.
    • Vytváření objektů je rychlejší.
    • Menší potřeba další paměti.
    Prototypová dědičnost v JavaScriptu

    JavaScript je velmi flexibilní jazyk. Prototypová dědičnost může být implementována oběma způsoby. Ve skutečnosti kód z příkladu implementace prototypové dědičnosti zkopírováním vlastností základního objektu (viz výše) funguje v JavaScriptu. JavaScript po vybalení však implementuje funkcionalitu podobnou metodě __get__ z druhého příkladu. Použití mechanismů rodného jazyka je podle mého názoru vhodnější pro implementaci dědičnosti, protože mohou být potenciálně optimalizovány jazykovým modulem.

    Chcete-li při přístupu k vlastnostem, které nejsou nastaveny na aktuálním objektu, odkazovat na základní objekt, použijte vlastnost []. To znamená, že chcete-li zdědit jeden objekt od druhého, musíte nějak nastavit dědice [] rovného odkazu na základní objekt. Nejjednodušší (ale ne standardizovaný na ES6 a ne nejrychlejší) způsob je použít vlastnost __proto__:

    Var základ = ( foo: "bar" ); var dítě = ( baz: 42, __proto__: základ ); console.log(child.baz); console.log(child.foo); // Delegujte volání na základní objekt

    Před ES6 existovaly minimálně dva „legální“ způsoby, jak to udělat. První a ne zrovna nejpřímější je použití nového klíčového slova. Promluvíme si o tom trochu později. Druhým je funkce Object.create() (odkaz) vynalezená Douglasem Crockfordem, která byla nakonec přidána do samotného jazyka.

    Kód z výše uvedeného příkladu lze přepsat následovně:

    Var základ = ( foo: "bar" ); var child = Object.create(base); // vytvoří nový objekt s daným prototypem child.baz = 42; console.log(child.baz); console.log(child.foo);

    Zabalením dvou řetězců vytvářejících potomka do funkce createChild() vytvoříme pohodlnou implementaci prototypové dědičnosti z base .

    Všechny matoucí nový design

    Hlavním důvodem, proč je obtížné porozumět implementaci dědičnosti v JavaScriptu, je nový konstrukt, přidaný do jazyka za účelem jeho popularizace, díky čemuž je podobný jazykům s „klasickým“ schématem dědičnosti.

    Jak bylo uvedeno výše, pomocí new můžete vytvořit objekt s daným prototypem. K tomu potřebujeme funkci.

    Var base = ( pozdrav: function() ( return "Ahoj " + tato.osoba; ) ); funkce Greeter(osoba) ( this.person = osoba; ) Greeter.prototype = základ; var greeter = new Greeter("Alice"); console.log(greeter.greeting()); // vypíše "Ahoj Alice" console.log(greeter.__proto__ === base); // vypíše "true"

    Funkce jako Greeter se v JavaScriptu (a někdy, ne zcela správně, třídách) nazývají konstruktory. Když se zavolá new Greeter(), vytvoří se nový objekt, který uvnitř konstruktoru odkazuje na tento objekt. A objekt Greeter.prototype je nastaven jako prototyp tohoto objektu. To zavádí další úroveň nepřímosti.

    Toto nasměrování bylo zamýšleno tak, aby se jazyk klasicky vyškoleným programátorům zdál známější, ale nepodařilo se to, jak můžeme vidět z velmi nízkého názoru programátorů Java na JavaScript. Vzor konstruktoru JavaScriptu neoslovil klasický dav. Také to zakrylo skutečnou prototypovou povahu JavaScriptu. V důsledku toho je velmi málo programátorů, kteří vědí, jak jazyk efektivně používat. (c) Douglas Crockford

    Nyní, s těmito znalostmi, můžeme snadno pochopit původní implementaci Object.create() :

    Object.create = function(o) ( function F() () F.prototype = o; return new F(); );

    Volání Object.create() vytvoří nový prázdný objekt (new F()), jehož prototypem bude objekt o. A toho je dosaženo díky výše popsané funkci JavaScriptu.

  • Po propuštění finální verze Specifikace ECMA Script 2015 (ES2015) dala komunitě příležitost posunout se směrem k její implementaci v JavaScriptových enginech.

    ES2015 přináší mnoho nových užitečných funkcí a čistší syntaxi stávajících funkcí. Například klíčové slovo class a vylepšená syntaxe prototypu JavaScriptu.

    Před ES2015 byla implementace dědičnosti prototypu pomocí JavaScriptu matoucí. V tradičním modelu třídy dědí ze tříd. Třídy nejsou nic jiného než specifikace nebo šablona používaná k vytváření objektů.

    Specifikace mohou dědit vlastnosti z jiných specifikací. Můžete vytvářet nové objekty pomocí zděděných a uživatelských vlastností. Na rozdíl od tradiční dědičnosti JavaScript nezahrnuje podporu pro tyto specifikace.

    Co je dědičnost prototypu JavaScriptu?

    Prototypová dědičnost v JavaScriptu zahrnuje jeden objekt dědění z jiného objektu, spíše než jedna specifikace dědí od jiného. I klíčové slovo new class je nesprávné, protože implikuje specifikaci. Ale ve skutečnosti jeden objekt dědí od druhého. Syntaxe ve více dřívější verze JavaScript byl příliš složitý a obtížně sledovatelný. Jakmile tedy vývojáři přijmou dědičnost mezi jednotlivými objekty, vyvstává druhá výzva. Spočívá ve vylepšení syntaxe dědičnosti prototypu JavaScriptu – představení tříd ES2015.

    Třídy ES2015 v JavaScriptu

    Tato specifikace poskytuje jasnější syntaxi pro definování struktur tříd, vytváření funkcí konstruktoru, rozšiřování tříd, volání konstruktoru a funkcí v supertřídě a poskytuje statické funkce. ES2015 také vylepšuje syntaxi pro vytváření getter/setter deskriptoru vlastností ve stylu ES5, což umožňuje vývojářům využít těchto málo známých funkcí specifikace.

    Definice tříd

    JavaScript neobsahuje třídy. Ani třídy ES2015 nejsou ve skutečnosti třídami v tradičním slova smyslu. A jen „vyčištěná“ syntaxe pro vytváření prototypové dědičnosti mezi objekty. Ale protože ES2015 používá termín „třída“ pro objekty vytvořené pomocí funkce konstruktoru (funkce konstruktoru je konečným výsledkem klíčového slova class), v tomto článku budeme termínem „třída“ popisovat nejen třídy ES2015, ale také jedničky ES5.

    V ES5 a dřívějších definovaly funkce konstruktoru „třídy“ takto:

    function MyClass() ( ) var myClass = new MyClass();

    ES2015 zavedl novou syntaxi pomocí klíčového slova class:

    class MyClass ( constructor() ( ) ) var myClass = new MyClass();

    Funkce konstruktoru zůstává stejná jako v ES5. Blok klíčového slova zabalené třídy definuje vlastnosti prototypu funkce JavaScript. Syntaxe nového klíčového slova pro nastavení nové instance třídy zůstává nezměněna.

    Se zavedením klíčového slova class existuje objekt funkce, který používá ES5. Zvažte následující výstup z rámce Node.js REPL. Nejprve definujeme nová třída a poté operátor TypeOf vypíše typy objektu třídy:

    > class MyClass ( constructor() () ) class MyClass ( constructor() () ) > typeof MyClass "funkce" >

    V ES2015 nebyla role a účel funkce konstruktoru revidována, její syntaxe byla jednoduše „vyčištěna“.

    Co jsou konstruktory v JavaScriptu?

    Konstruktor je funkce, která se provede, když se operátor new použije k vytvoření nové instance třídy. Argumenty lze předat funkci konstruktoru pro inicializaci vlastností objektu a provádění dalších úkolů.

    V ES5 funkce konstruktoru vypadá takto:

    function Osoba(jméno, příjmení) ( this.firstName = křestní jméno; this.lastName = příjmení; )

    Ekvivalent funkce konstruktoru se syntaxí ES2015 vypadá takto:

    // název funkce konstruktoru ES5 - // toto je název osoby třídy ES2015 ( // všimněte si, že zde není žádné klíčové slovo "funkce" // je také použito slovo "konstruktor", nikoli konstruktor "Osoba" (firstName, lastName) ( // tento kód představuje nově vytvořený a // inicializovaný objekt this.firstName = firstName; this.lastName = lastName;

    Přestože je nová syntaxe delší, je přehlednější a usnadňuje přidávání zděděných vlastností.

    Chcete-li nastavit objekt se stejnou syntaxí, kód musí být stejný:

    var osoba = nová osoba("Bob", "Smith"); // vypíše "Bob" console.log(person.firstName); // vypíše "Smith" console.log(person.lastName);

    Kliknutím sem stáhnete kód

    Rozšíření třídy

    Před ES2015 většina vývojářů nechápala, jak implementovat dědičnost mezi objekty a používat prototyp JavaScriptu. Pokud si promluvíte s vývojáři C++, Java nebo C#, pochopíte, jak snadno mohou nastavit jednu třídu tak, aby dědila od druhé, a pak vytvořit instanci objektu z podtřídy. Požádejte vývojáře JavaScriptu, aby předvedl, jak funguje dědičnost mezi dvěma objekty, a dostanete prázdný pohled.

    Nastavení dědičnosti prototypu není snadné a koncept dědičnosti prototypu většina vývojářů JavaScriptu nezná. Zde je několik příkladů kódu s komentáři, které vysvětlují proces nastavení dědičnosti:

    // volá se operátorem "new", // je vytvořen nový objekt Person funkce Osoba(jméno, příjmení) ( // operátor "new" naváže spojení // z "toho" na nový objekt this.firstName = firstName; this.lastName = lastName ) // tato vlastnost vazby funkce je // konfigurována na objektu prototypu Person, // a zděděná Student Person.prototype.getFullName = function() ( return this.firstName + " " + this. příjmení; // Když je funkce konstruktoru Student // volána s operátorem "new", // se vytvoří nový objekt Student funkce Student(studentId, firstName, lastName) ( // operátor "new" vytvoří odkaz z "toto" do // nového objektu, nový je pak objekt předán // funkci konstruktoru Osoba pomocí volání // takže vlastnosti jména a příjmení lze nastavit takto._super.call(this, firstName, lastName) ; this.studentId = studentId ) // Student je zděděn z objektu new; // nastaví vlastnosti konstruktoru zpět pro // funkci studentského konstruktoru Student.prototype.constructor = Student; // "_super" NENÍ součástí ES5, jeho vývojářem definované sady konvencí // "_super" pro funkci konstruktoru Osoba Student.prototype._super = Osoba; // toto bude existovat v prototypu studentského objektu Student.prototype.getStudentInfo = function() ( return this.studentId + " " + this.lastName + ", " + this.firstName; ); // nastavení nového objektu Student var student = new Student(1, "Bob", "Smith"); // zavolá funkci ve výstupu nadřazeného // prototypu "Bob Smith" console.log(student.getFullName()); // zavolá funkci ve výstupu nadřazeného // prototypu "1 Smith, Bob" console.log(student.getStudentInfo());

    Kliknutím sem stáhnete kód

    Výše uvedený kód se obtížně analyzuje a trvá dlouho, než se vytvoří dědičnost z jednoho objektu na druhý při zachování funkčnosti konstruktoru. Většina vývojářů JavaScriptu nedokáže vytvořit tento kód z paměti a mnozí nikdy nic takového při práci s JavaScriptem neviděli.

    K vyřešení tohoto problému bylo v nové syntaxi struktury třídy v ES2015 zavedeno klíčové slovo extends. Následující kód demonstruje stejnou dědičnost jako první příklad kódu, ale s použitím syntaxe prototypu objektu ES2015 JavaScript:

    "použít přísné"; class Osoba ( konstruktor(jméno, příjmení) ( this.firstName = jméno; this.lastName = příjmení; ) getFullName() ( return this.firstName + " " + this.lastName; ) ) class Student extends Person ( constructor(studentId, jméno, příjmení) ( super(jméno, příjmení); this.studentId = studentId; ) getStudentInfo() ( return this.studentId + " " + this.lastName + ", " + this.firstName; ) ) var student = new Student (1, "Bob", "Smith"); console.log(student.getFullName()); console.log(student.getStudentInfo());

    Kliknutím sem stáhnete kód

    Je zřejmé, že druhý přístup je srozumitelnější. Oba kódy reprodukují stejnou strukturu objektu. Nová syntaxe tedy zlepšuje kód dědičnosti, ale nemění výsledek.

    Dalším způsobem, jak prozkoumat, jak to funguje, je podívat se na kód dědičnosti ES5 generovaný TypeScriptem. TypeScript je preprocesorový jazyk, který optimalizuje JavaScript prostřednictvím silného psaní a transpilace kódu ES2015 do kódu ES5. Transpiling je proces kompilace zdrojového kódu jednoho programovacího jazyka do zdrojový kód jiný jazyk.

    _extends funkce v JavaScriptu

    Pro podporu dědičnosti tříd ES2015 TypeScript transpiluje funkci klíčových slov extends do funkce nazvané __extends, která spouští kód potřebný k nastavení dědičnosti. Zde je kód pro funkci __extends:

    var __extends = (toto && toto.__extends) || funkce (d, b) ( pro (var p v b) if (b.hasOwnProperty(p)) d[p] = b[p]; funkce __() ( this.constructor = d; ) d.prototype = b === null ? Object.create(b) : (__.prototyp = b.prototyp, nový __());

    Výše uvedený kód je trochu složitý, takže níže je rozšířená zdokumentovaná verze. Abyste pochopili účel každého řádku kódu, přečtěte si komentáře přidané do zdrojového kódu prototypu JavaScriptu. Funkce __extends funguje na libovolném páru nadřazených a podřízených objektů:

    // deklaruje proměnnou pro vazbu funkce extends var __extends; if (this && this.__extends) ( // funkce extends je již definována v kontextu // tohoto kódu, takže použijte stávající funkce __extends __extends = toto.__extends; ) jinak (

    Zbytek obsahu bloku je implementací funkce __extends. Používá jak vzor mixin, tak dědičnost prototypu JavaScriptu k vytvoření vztahu dědičnosti mezi nadřazenými a podřízenými objekty. Vzor mixin kopíruje vlastnosti z jednoho objektu do druhého. Níže uvedený kód je zpracován pomocí funkce __extends:

    // funkce extends ještě není v aktuálním kontextu definována; // tak to definuj __extends = function (child, parent) ( // mixin template pro kopírování vlastností funkce nadřazeného konstruktoru // jako statické vlastnosti pro vlastnosti funkce podřízeného konstruktoru // ve funkci konstruktoru se často nazývá statická vlastnost pro (var parentPropertyName in parent ) ( // pouze zkopírované vlastnosti jsou samostatně definovány pro rodiče if (parent.hasOwnProperty(parentPropertyName)) ( // pro nejjednodušší typy tento kód kopíruje hodnoty, // pro typy objektů tento kód kopíruje pouze vztahy child = parent; ) ) // konstruktor funkce pro objekt, který nastavil potomka // zděděný z této funkce // je jedinečný v kontextu každého volání funkce extend __() ( this.constructor = potomek; ) if (rodič === null) ( // objekt, nastavený funkcí podřízeného konstruktoru // dědí z objektu, který zase z ničeho nedědí, // ani vestavěný objekt JavaScript child.prototype = Object.create(parent);

    ) else ( // přiřadit vlastnosti prototypu rodičovské funkce konstruktoru // k vlastnostem prototypu funkce konstruktoru definované výše __.prototype = parent.prototype; // vytvořit objekt, ze kterého dědí všechny podřízené instance, // a přiřadit to do vlastnosti prototypu funkce dítěte / / konstruktoru child.prototype = new __();

    Následující dva řádky kódu mate mnoho vývojářů:

    //přiřadí vlastnost prototyp rodičovské funkce konstruktoru //vlastnosti prototyp funkce konstruktoru definované výše __.prototype = parent.prototype; // vytvoří objekt, ze kterého // dědí všechny podřízené instance a přiřadí jej vlastnosti prototypu potomka // funkce konstruktoru child.prototype = new __();

    Možná si myslíte, že kód by měl být napsán takto:

    Vývojáři se mylně domnívají, že podřízený objekt bude nyní dědit z objektu prototypu funkce nadřazeného konstruktoru. Ale ve skutečnosti objekty vytvořené pomocí funkce nadřazeného konstruktoru, stejně jako objekty vytvořené pomocí funkce podřízeného konstruktoru, dědí z přesně stejného prototypu objektu JavaScript. To je nežádoucí, protože vlastnost prototypu funkce podřízeného konstruktoru nelze změnit bez současné změny vlastnosti prototypu funkce nadřazeného konstruktoru. Veškeré změny provedené v podřízeném objektu se proto použijí i na nadřazený objekt. Toto je nesprávná dědičnost:

    Když vytvoříte novou instanci objektu pomocí operátoru new a nadřazené nebo podřízené funkce konstruktoru, výsledné objekty zdědí ze stejného prototypového objektu (PPO). Zavedené nadřazené a podřízené objekty jsou objekty na stejné úrovni s PPO jako nadřazený objekt. Podřízený objekt nedědí od svého rodiče.

    Účelem tohoto kódu je tedy vytvořit následující strukturu dědičnosti:

    Prototyp = rodič.prototyp; child.prototype = new __();


    Díky této nové struktuře dědí nové podřízené objekty z CPO (Children Prototype Object), který dědí z PPO. K CPO lze přidat nové vlastnosti, které neovlivní PPO. Nové nadřazené objekty jsou zděděny z RPO a nejsou ovlivněny změnami RPO. Změny objektu PPO budou zděděny objektem vytvořeným pomocí rodičovské i podřízené funkce konstruktoru. S touto novou strukturou jsou podřízené objekty zděděny od rodiče.

    A na konci se uzavírací složená závorka vztahuje k původnímu bloku if:

    Kliknutím sem stáhnete kód

    Syntaxe ES2015 pro rozšiřování tříd je mnohem srozumitelnější než prototyp JavaScriptu. Obsahuje dvě nová klíčová slova: extends a super . Klíčové slovo extends vytváří prototyp dědičného vztahu mezi nadřazenými a podřízenými třídami. Klíčové slovo super volá konstruktor pro nadřazenou třídu (aka superclass). Volání super je vyžadováno i v případě, že nadřazený objekt neobsahuje konfiguraci.



  • 
    Nahoru