11. února 2025

F.prototype

Pamatujte, že nové objekty mohou být vytvořeny konstruktorem, například new F().

Pokud vlastnost F.prototype obsahuje objekt, pak operátor new jej nastaví jako [[Prototype]] nově vytvořenému objektu.

Poznámka:

JavaScript měl prototypovou dědičnost již od začátku. Byl to jeden z klíčových prvků jazyka.

V dřívějších dobách k ní však nebyl přímý přístup. Spolehlivě fungovala jedině vlastnost konstruktoru "prototype", popsaná v této kapitole. Existuje tedy mnoho skriptů, které ji stále využívají.

Prosíme všimněte si, že F.prototype zde znamená regulérní vlastnost jménem "prototype" funkce F. Trochu se to podobá pojmu „prototyp“, ale tady máme opravdu na mysli obvyklou vlastnost s tímto názvem.

Zde je příklad:

let zvíře = {
  žere: true
};

function Králík(jméno) {
  this.jméno = jméno;
}

Králík.prototype = zvíře;

let králík = new Králík("Bílý králík"); //  králík.__proto__ == zvíře

alert( králík.žere ); // true

Nastavení Králík.prototype = zvíře znamená doslova toto: „Když bude vytvořen new Králík, přiřaď zvíře do jeho vlastnosti [[Prototype]]“.

Výsledný obrázek je následující:

Na obrázku je "prototype" vodorovná šipka, která znamená regulérní vlastnost, a [[Prototype]] je svislá šipka, která znamená dědění objektu králík z objektu zvíře.

F.prototype bude použita jen při new F

Vlastnost F.prototype bude použita, jen když je volán new F. Přiřadí [[Prototype]] novému objektu.

Jestliže se po vytvoření objektu vlastnost F.prototype změní (F.prototype = <jiný objekt>), pak nové objekty vytvořené pomocí new F budou mít jako [[Prototype]] jiný objekt, ale již existujícím objektům zůstane starý prototyp.

Standardní F.prototype, vlastnost konstruktoru

Vlastnost "prototype" má každá funkce, i když ji neuvedeme.

Standardní "prototype" je objekt s jedinou vlastností constructor, která ukazuje zpět na samotnou funkci.

Například takto:

function Králík() {}

/* standardní prototyp
Králík.prototype = { constructor: Králík };
*/

Můžeme si to ověřit:

function Králík() {}
// standardně:
// Králík.prototype = { constructor: Králík }

alert( Králík.prototype.constructor == Králík ); // true

Jestliže nic nezměníme, bude vlastnost constructor dostupná skrz [[Prototype]] všem králíkům:

function Králík() {}
// standardně:
// Králík.prototype = { constructor: Králík }

let králík = new Králík(); // dědí z {constructor: Králík}

alert(králík.constructor == Králík); // true (z prototypu)

Vlastnost constructor můžeme použít k vytvoření nového objektu stejným konstruktorem, jakým byl vytvořen stávající objekt.

Například zde:

function Králík(jméno) {
  this.jméno = jméno;
  alert(jméno);
}

let králík = new Králík("Bílý králík");

let králík2 = new králík.constructor("Černý králík");

To se hodí, když máme objekt, nevíme, jaký konstruktor pro něj byl použit (např. protože pochází z knihovny třetí strany), a potřebujeme vytvořit další objekt stejného druhu.

Asi nejdůležitější věcí na "constructor" je však to, že…

…samotný JavaScript správnou hodnotu vlastnosti "constructor" nezajišťuje.

Ano, nachází se ve standardní vlastnosti funkcí "prototype", ale to je vše. Co se s ní stane později, je zcela na nás.

Konkrétně, jestliže standardní prototyp úplně nahradíme něčím jiným, žádný "constructor" v něm nebude.

Například:

function Králík() {}
Králík.prototype = {
  skáče: true
};

let králík = new Králík();
alert(králík.constructor === Králík); // false

Abychom tedy udrželi správný "constructor", můžeme se rozhodnout, že budeme vlastnosti přidávat do a odebírat ze standardního "prototype", místo abychom jej úplně měnili:

function Králík() {}

// Nepřepíšeme Králík.prototype úplně
// jen do něj něco přidáme
Králík.prototype.skáče = true
// standardní Králík.prototype.constructor se zachová

Nebo alternativně vlastnost constructor vytvoříme znovu ručně:

Králík.prototype = {
  skáče: true,
  constructor: Králík
};

// nyní je constructor také korektní, protože jsme jej přidali

Shrnutí

V této kapitole jsme krátce popsali způsob, jak nastavit [[Prototype]] pro objekty vytvořené konstruktorem. Později uvidíme pokročilejší programovací vzory, které jsou na tom postaveny.

Všechno je zcela jednoduché, jen několik poznámek, abychom si to ujasnili:

  • Vlastnost F.prototype (nepleťte si ji s [[Prototype]]) nastaví [[Prototype]] nových objektů, když je zavolán new F().
  • Hodnota F.prototype by měla být buď objekt, nebo null: jiné hodnoty nefungují.
  • Vlastnost "prototype" má tento speciální efekt jen tehdy, když je nastavena na konstruktoru a ten je zavolán pomocí new.

Na běžných objektech prototype neznamená nic zvláštního:

let uživatel = {
  jméno: "Jan",
  prototype: "Ble-ble" // vůbec žádná magie
};

Standardně mají všechny funkce F.prototype = { constructor: F }, takže můžeme získat konstruktor objektu přístupem k jeho vlastnosti "constructor".

Úlohy

důležitost: 5

V následujícím kódu vytvoříme new Králík a pak se pokusíme změnit jeho prototyp.

Na začátku máme tento kód:

function Králík() {}
Králík.prototype = {
  žere: true
};

let králík = new Králík();

alert( králík.žere ); // true
  1. Přidáme další řádek (zvýrazněný). Co nyní zobrazí alert?

    function Králík() {}
    Králík.prototype = {
      žere: true
    };
    
    let králík = new Králík();
    
    Králík.prototype = {};
    
    alert( králík.žere ); // ?
  2. …A když kód vypadá takto (jeden řádek změněn)?

    function Králík() {}
    Králík.prototype = {
      žere: true
    };
    
    let králík = new Králík();
    
    Králík.prototype.žere = false;
    
    alert( králík.žere ); // ?
  3. A takto (jeden řádek změněn)?

    function Králík() {}
    Králík.prototype = {
      žere: true
    };
    
    let králík = new Králík();
    
    delete králík.žere;
    
    alert( králík.žere ); // ?
  4. Poslední varianta:

    function Králík() {}
    Králík.prototype = {
      žere: true
    };
    
    let králík = new Králík();
    
    delete Králík.prototype.žere;
    
    alert( králík.žere ); // ?

Odpovědi:

  1. true.

    Přiřazení do Králík.prototype nastaví [[Prototype]] pro nové objekty, ale neovlivní již existující.

  2. false.

    Objekty jsou přiřazovány odkazem. Objekt z Králík.prototype není duplikován, ale je to stále jediný objekt, na který se odkazují jak Králík.prototype, tak [[Prototype]] objektu králík.

    Když tedy změníme jeho obsah skrz jeden odkaz, uvidíme to i druhým odkazem.

  3. true.

    Všechny operace delete se aplikují přímo na objekt. Zde se delete králík.žere pokusí odstranit vlastnost žere z objektu králík, ale ten ji neobsahuje. Operace tedy nemá žádný účinek.

  4. undefined.

    Vlastnost žere je smazána z prototypu a nadále neexistuje.

důležitost: 5

Představme si, že máme libovolný objekt obj, který je vytvořen konstruktorem – nevíme jakým, ale rádi bychom pomocí něj vytvořili nový objekt.

Můžeme to udělat takto?

let obj2 = new obj.constructor();

Uveďte příklad konstruktoru pro obj, pro který by takový kód správně fungoval. A příklad, pro který by fungoval špatně.

Tento přístup můžeme použít, pokud jsme si jisti, že vlastnost "constructor" obsahuje správnou hodnotu.

Například pokud se nedotkneme standardní vlastnosti "prototype", pak bude tento kód zaručeně fungovat:

function Uživatel(jméno) {
  this.jméno = jméno;
}

let uživatel = new Uživatel('Jan');
let uživatel2 = new uživatel.constructor('Petr');

alert( uživatel2.jméno ); // Petr (funguje!)

Funguje to, protože Uživatel.prototype.constructor == Uživatel.

…Ale pokud někdo dejme tomu přepíše Uživatel.prototype a zapomene znovu vytvořit constructor, aby odkazoval na Uživatel, pak to selže.

Například:

function Uživatel(jméno) {
  this.jméno = jméno;
}
Uživatel.prototype = {}; // (*)

let uživatel = new Uživatel('Jan');
let uživatel2 = new uživatel.constructor('Petr');

alert( uživatel2.jméno ); // undefined

Proč je uživatel2.jméno undefined?

Takto funguje new uživatel.constructor('Petr'):

  1. Nejprve hledá constructor v objektu uživatel. Nic.
  2. Pak postupuje řetězcem prototypů. Prototyp objektu uživatel je Uživatel.prototype a ani ten nemá žádný constructor (protože jsme ho „zapomněli“ správně nastavit!).
  3. Postupuje řetězcem dále nahoru. Uživatel.prototype je planý objekt a jeho prototypem je vestavěný Object.prototype.
  4. Nakonec pro vestavěný Object.prototype je vestavěný Object.prototype.constructor == Object. Ten se tedy použije.

Nakonec tedy máme let uživatel2 = new Object('Petr').

To pravděpodobně není to, co chceme. Rádi bychom vytvořili new Uživatel, ne new Object. To je důsledek chybějící vlastnosti constructor.

(Jen pro případ, že vás to zajímá: volání new Object(...) konvertuje svůj argument na objekt. To je teoretická záležitost, v praxi nikdo nevolá new Object s hodnotou a obecně vůbec nepoužíváme new Object k vytváření objektů.)

Mapa tutoriálu

Komentáře

přečtěte si před komentováním…
  • Máte-li návrhy na zlepšení, vytvořte prosím issue na GitHubu nebo pull request místo komentáře.
  • Pokud v článku něčemu nerozumíte, napište prosím, čemu přesně a na kterém místě.
  • Pro vložení několika slov kódu použijte značku <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…)