V prvnà kapitole této sekce jsme se zmÃnili, že existujà modernà metody, jak nastavit prototyp.
Nastavovánà nebo Ätenà prototypu pomocà obj.__proto__ je považováno za zastaralé a je mÃrnÄ zavrhováno (je pÅesunuto do Äásti standardu JavaScriptu nazvané âPÅÃloha Bâ, která je urÄena výhradnÄ pro prohlÞeÄe).
Modernà metody pro naÄtenà a nastavenà prototypu jsou:
- Object.getPrototypeOf(obj) â vrátÃ
[[Prototype]]objektuobj. - Object.setPrototypeOf(obj, proto) â nastavÃ
[[Prototype]]objektuobjnaproto.
Jediné využità __proto__, nad kterým se lze nezamraÄit, je použÃt ho jako vlastnost pÅi vytvoÅenà nového objektu: { __proto__: ... }.
AÄkoli i k tomu existuje speciálnà metoda:
- Object.create(proto[, deskriptory]) â vytvoÅà prázdný objekt, jehož
[[Prototype]]budeproto, s možnostà uvést jeho deskriptory vlastnostÃ.
NapÅÃklad:
let zvÃÅe = {
žere: true
};
// vytvoÅà nový objekt, jehož prototypem je zvÃÅe
let králÃk = Object.create(zvÃÅe); // totéž jako {__proto__: zvÃÅe}
alert(králÃk.žere); // true
alert(Object.getPrototypeOf(králÃk) === zvÃÅe); // true
Object.setPrototypeOf(králÃk, {}); // zmÄnà prototyp objektu králÃk na {}
Metoda Object.create je trochu silnÄjÅ¡Ã, jelikož má nepovinný druhý argument: deskriptory vlastnostÃ.
V nich můžeme novému objektu poskytnout dalšà vlastnosti, napÅÃklad:
let zvÃÅe = {
žere: true
};
let králÃk = Object.create(zvÃÅe, {
skáÄe: {
value: true
}
});
alert(králÃk.skáÄe); // true
Deskriptory jsou ve stejném formátu, jaký byl popsán v kapitole PÅepÃnaÄe a deskriptory vlastnostÃ.
Pomocà Object.create můžeme provádÄt klonovánà objektů, které je silnÄjšà než kopÃrovánà vlastnostà v cyklu for..in:
let klon = Object.create(
Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)
);
Toto volánà vytvoÅà opravdu pÅesnou kopii obj, vÄetnÄ vÅ¡ech vlastnostÃ: enumerovatelných i neenumerovatelných, datových vlastnostà i setterů a getterů â se vÅ¡Ãm a navÃc se správným [[Prototype]].
Krátká historie
Způsobů, jak pracovat s [[Prototype]], existuje velké množstvÃ. Jak k tomu doÅ¡lo a proÄ?
Je tomu tak z historických důvodů.
Prototypová dÄdiÄnost byla v jazyce obsažena již od jeho zaÄátků, ale způsoby, jak s nà manipulovat, se s Äasem vyvÃjely.
- Vlastnost konstruktoru
"prototype"fungovala již od pradávných Äasů. Je to nejstaršà způsob, jak vytváÅet objekty se zadaným prototypem. - PozdÄji, v roce 2012, se ve standardu objevila metoda
Object.create, která poskytla možnost vytváÅet objekty se zadaným prototypem, ale neposkytla možnost jej naÄÃtat nebo nastavovat. NÄkteré prohlÞeÄe tedy implementovaly nestandardnà pÅÃstupovou vlastnost__proto__, která umožÅovala uživateli kdykoli naÄÃtat nebo nastavovat prototyp, aby poskytly vývojáÅům vÄtšà flexibilitu. - PozdÄji, v roce 2015, byly do standardu pÅidány metody
Object.setPrototypeOfaObject.getPrototypeOf, které poskytujà stejnou funkcionalitu jako__proto__. Jelikož__proto__bylo implementováno de facto vÅ¡ude, bylo tak trochu zavrženo a dostalo se do PÅÃlohy B standardu, což znamená, že pro jiná než prohlÞeÄová prostÅedà nenà povinné. - PozdÄji, v roce 2022, bylo oficiálnÄ umožnÄno použÃvat
__proto__v objektových literálech{...}(pÅesunuto z PÅÃlohy B), ale ne jako getter/setterobj.__proto__(stále v PÅÃloze B).
ProÄ bylo __proto__ nahrazeno funkcemi getPrototypeOf/setPrototypeOf?
ProÄ bylo __proto__ ÄásteÄnÄ rehabilitováno a jeho použità dovoleno v {...}, ale ne jako getter/setter?
To je zajÃmavá otázka, která od nás vyžaduje, abychom pochopili, proÄ je __proto__ Å¡patné.
Brzy se dozvÃme odpovÄÄ.
[[Prototype]] na existujÃcÃch objektechTechnicky můžeme [[Prototype]] nastavit nebo naÄÃst kdykoli. Obvykle jej vÅ¡ak nastavujeme jen jednou v okamžiku vytvoÅenà objektu a pak už jej nemÄnÃme: králÃk dÄdà ze zvÃÅe a to se nebude mÄnit.
JavaScriptové motory jsou na to vysoce optimalizované. ZmÄna prototypu âza bÄhuâ pomocà Object.setPrototypeOf nebo obj.__proto__= je velmi pomalá operace, jelikož rozbÃjà vnitÅnà optimalizace operacà pÅÃstupu k objektovým vlastnostem. Proto se jà zdržte, s výjimkou situacÃ, kdy dobÅe vÃte, co dÄláte, nebo kdy vám na rychlosti JavaScriptu vůbec nezáležÃ.
âVelmi planéâ objekty
Jak vÃme, objekty můžeme použÃvat jako asociativnà pole k ukládánà dvojic klÃÄ/hodnota.
â¦Pokud se do nÄj vÅ¡ak pokusÃme uložit uživatelem poskytnuté klÃÄe (napÅÃklad slovnÃk s uživatelskými vstupy), uvidÃme zajÃmavou závadu: vÅ¡echny klÃÄe fungujà správnÄ až na "__proto__".
OvÄÅte si to na pÅÃkladu:
let obj = {};
let klÃÄ = prompt("Jaký je klÃÄ?", "__proto__");
obj[klÃÄ] = "nÄjaká hodnota";
alert(obj[klÃÄ]); // [object Object], ne "nÄjaká hodnota"!
Když zde uživatel zadá __proto__, pÅiÅazenà na Åádku 4 je ignorováno!
To může být pro nevývojáÅe jistÄ pÅekvapenÃm, ale my bychom tomu mÄli rozumÄt. Vlastnost __proto__ je speciálnÃ: musà to být buÄ objekt, nebo null. ÅetÄzec nemůže být prototypem. Z tohoto důvodu je pÅiÅazenà ÅetÄzce do __proto__ ignorováno.
Takové chovánà jsme vÅ¡ak nezamýšleli implementovat, že? Chceme ukládat dvojice klÃÄ/hodnota a klÃÄ jménem "__proto__" nebyl správnÄ uložen. Takže je to chyba!
Zde důsledky nejsou hrozivé. V jiných pÅÃpadech vÅ¡ak můžeme do obj pÅiÅazovat objekty mÃsto ÅetÄzců a pak se prototyp zcela jistÄ zmÄnÃ. Výsledkem bude, že se bÄh skriptu pokazà naprosto neÄekanými způsoby.
Co je horšà â vývojáÅi o takové možnosti vÄtÅ¡inou vůbec nepÅemýšlejÃ. To Äinà takové chyby obtÞnÄ zaznamenatelnými a dokonce způsobuje zranitelnost, zvláštÄ když je JavaScript použit na stranÄ serveru.
NeÄekané vÄci se mohou dÃt i tehdy, když pÅiÅazujeme do obj.toString, protože je to vestavÄná objektová metoda.
Jak se můžeme tomuto problému vyhnout?
Za prvé, mÃsto planých objektů můžeme k ukládánà použÃt Map, pak bude vÅ¡echno v poÅádku:
let mapa = new Map();
let klÃÄ = prompt("Jaký je klÃÄ?", "__proto__");
mapa.set(klÃÄ, "nÄjaká hodnota");
alert(mapa.get(klÃÄ)); // "nÄjaká hodnota" (jak jsme zamýšleli)
â¦AvÅ¡ak syntaxe Object je Äasto lákavÄjÅ¡Ã, protože je struÄnÄjÅ¡Ã.
NaÅ¡tÄstà můžeme použÃt objekty, protože autoÅi jazyka na tento problém mysleli už pÅed dlouhou dobou.
Jak vÃme, __proto__ nenà vlastnost objektu, ale pÅÃstupová vlastnost Object.prototype:
Jestliže je tedy obj.__proto__ Ätena nebo nastavována, z prototypu objektu se volá odpovÃdajÃcà getter Äi setter a ten [[Prototype]] naÄte nebo nastavÃ.
Jak bylo ÅeÄeno na zaÄátku této sekce tutoriálu: __proto__ je způsob pÅÃstupu k [[Prototype]], nenà to samotný [[Prototype]].
Jestliže tedy zamýšlÃme použÃvat objekt jako asociativnà pole a takovým problémům se vyhnout, můžeme to udÄlat malým trikem:
let obj = Object.create(null);
// or: obj = { __proto__: null }
let klÃÄ = prompt("Jaký je klÃÄ?", "__proto__");
obj[klÃÄ] = "nÄjaká hodnota";
alert(obj[klÃÄ]); // "nÄjaká hodnota"
Object.create(null) vytvoÅà prázdný objekt bez prototypu ([[Prototype]] je null):
Pro __proto__ tedy nebude zdÄdÄn žádný getter/setter. Nynà se bude zpracovávat jako bÄžná datová vlastnost, takže uvedený pÅÃklad bude fungovat správnÄ.
Takové objekty můžeme nazývat âvelmi planéâ nebo âÄistÄ slovnÃkovéâ objekty, protože jsou jeÅ¡tÄ jednoduššà než obvyklý planý objekt {...}.
Nevýhodou je, že takový objekt postrádá veÅ¡keré vestavÄné objektové metody, napÅ. toString:
let obj = Object.create(null);
alert(obj); // Chyba (nenà toString)
â¦To je vÅ¡ak u asociativnÃch polà zpravidla dobÅe.
VÅ¡imnÄte si, že vÄtÅ¡ina metod vztahujÃcÃch se k objektům jsou Object.nÄco(...), napÅ. Object.keys(obj) â nejsou v prototypu, takže budou na takových objektech fungovat:
let ÄÃnskýSlovnÃk = Object.create(null);
ÄÃnskýSlovnÃk.ahoj = "ä½ å¥½";
ÄÃnskýSlovnÃk.sbohem = "åè§";
alert(Object.keys(ÄÃnskýSlovnÃk)); // ahoj,sbohem
ShrnutÃ
-
K vytvoÅenà objektu se zadaným prototypem použijeme:
- literálnà syntaxi:
{ __proto__: ... }, která umožÅuje specifikovat vÃce vlastnostÃ, - nebo Object.create(proto[, deskriptory]), která umožÅuje specifikovat deskriptory vlastnostÃ.
Metoda
Object.createposkytuje snadný způsob, jak vytvoÅit mÄlkou kopii objektu se vÅ¡emi deskriptory:let klon = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); - literálnà syntaxi:
-
Modernà metody pro naÄtenà a nastavenà prototypu jsou:
- Object.getPrototypeOf(obj) â vrátÃ
[[Prototype]]objektuobj(totéž jako getter__proto__). - Object.setPrototypeOf(obj, proto) â nastavÃ
[[Prototype]]objektuobjna hodnotuproto(totéž jako setter__proto__).
- Object.getPrototypeOf(obj) â vrátÃ
-
NaÄtenà a nastavenà prototypu pomocà vestavÄného getteru/setteru
__proto__se nedoporuÄuje a nynà je ve specifikaci obsaženo v PÅÃloze B. -
Uvedli jsme i objekty bez prototypů, vytvoÅené pomocÃ
Object.create(null)nebo{__proto__: null}.Tyto objekty se použÃvajà jako slovnÃky, aby do nich bylo možné uložit jakékoli klÃÄe (tÅeba i vygenerované uživatelem).
NormálnÄ objekty dÄdà z
Object.prototypevestavÄné metody a getter/setter__proto__, což znamená, že odpovÃdajÃcà klÃÄe jsou âobsazenyâ, a může to způsobit vedlejšà efekty. Je-li prototypnull, objekty jsou skuteÄnÄ prázdné.
KomentáÅe
<code>, pro nÄkolik Åádků je obalte znaÄkou<pre>, pro vÃce než 10 Åádků vložte odkaz na pÃskoviÅ¡tÄ (plnkr, jsbin, codepenâ¦)