Prototypy v JS

Motivace

V poslední době jsem četl spoustu článků o základních konceptech Javascriptu. Po přečtení mnoha z nich jsem byl ohromen tolika vědomostmi najednou. Ačkoli jsem psal klíčové body každého konceptu v dokumentech, uvědomil jsem si, že by pro mě bylo lepší vytvořit podrobné články pro koncepty, které jsem se učil.

Nyní začněme.

Příklad kódu

Podívejme se nejprve na malý program

function Person(name,age){
  this.name = name;
  this.age = age;
}

let john = new Person('John',24)

Výše uvedený program má konstruktor Person a vytvoří svou instanci john . Pojďme to přihlásit do konzole prohlížeče

Zde máme [[Prototype]] vlastnost definovaná mimo tyto dvě vlastnosti. Při dalším otevření máme constructor s hodnotou Person funkce a [[Prototype]] definován znovu a dále má seznam vlastností.
Když to vidíme, můžeme dojít k závěru, že za tím se děje spousta věcí, protože definujeme jen jednoduchou funkci. Pro větší jasnost to musíme pochopit. Za tímto účelem se nejprve seznámíme s Prototypem.

Co je prototyp?

Podle MDN,

Tím, že to pochopíte, určitě existuje něco, co souvisí s dědictvím. Pojďme to definovat trochu jasněji.

Prototyp je objekt, který je dostupný u každé funkce, kterou definujeme v kódu JS.

Skládá se ze dvou věcí :-

  • A constructor ukazující zpět na funkci, na které je prototype byl definován
  • __proto__ (dunder proto) objekt mající hodnotu prototypu prototypu aktuální funkce. Je to stejné jako [[Prototype]] ale přistupujeme k němu pomocí __proto__ . Pro tento článek použijme __proto__ místo [[Prototype]]

Tyto dvě věci by bylo pro tento článek velmi důležité pochopit.

Pro znázornění použijeme konstruktor definovaný výše.

Nyní pro totéž vytvoříme dva objekty.

let john = new Person('John',24);
let james = new Person('James',20);

Protokolování john a james na konzole nám to dává

Zde vidíme, že oba mají požadovanou sadu vlastností, jak je definováno v Person a mají __proto__ objekt definován.
Při dalším otevření vidíme, že má Person konstruktor a __proto__ objekt definován. Oba jsou stejné jako prototyp osoby. Zkontrolujme, zda jsou oba stejné pomocí referenční rovnosti.

console.log(Person.prototype === john.__proto__); // true

Proto docházíme k závěru, že:-

  • Prototyp konstruktoru je stejný jako prototyp jeho instance. K prototypu konstruktoru můžeme přistupovat pomocí .prototype při použití .__proto__ pro tento případ.
  • Všechny instance sdílejí stejný prototypový objekt konstruktoru.

Nyní, když jsme to hodně pochopili, pojďme rozšířit prototyp Person Nyní.

Zde vidíme, že Person má prototyp Object který má opět prototyp Object do null . Tento koncept propojení prototypového objektu s prototypy nadřazených konstruktorů až do null se nazývá řetězení prototypů.

Několik postřehů k vlastnostem prototypu

POZNÁMKA: Příklady použité v této části slouží pouze pro účely porozumění a nemají být použity pro vytváření objektů reálného světa.

  • Vlastnosti objektů jsou přístupné až nahoru prostřednictvím řetězce prototypů

Pojďme nyní předefinovat náš konstruktor jiným způsobem a vytvořit pro něj objekt.

function Person(){}
Person.prototype.name = "John";
Person.prototype.age = 23;

let john = new Person();

console.log(john); // {}
console.log(john.name);  // 'John'
console.log(john.age);  // 23

Zde vidíme, že máme přístup k vlastnostem john které nejsou definovány v konstruktoru, ale jsou definovány v jeho prototypu.
Toho bylo dosaženo pomocí řetězení prototypů. Všechny její rodičovské prototypy jsou prohledány pro vlastnost, dokud nenarazíme na požadovanou vlastnost, ke které má být přístup. To znamená, že pokud bychom nedefinovali name a age v prototypu všechny prototypy john byly rekurzivně vyhledávány pro vlastnost, která nebyla definována ani u posledního objektu v řetězci prototypu, což by nám dalo undefined .

function Person(){}
let john = new Person();

console.log(john); // {}
console.log(john.name);  // undefined
console.log(john.age);  // undefined
  • Nastavení vlastnosti objektu z něj udělá přímého potomka, i když již byl definován ve svém prototypu
function Person(){}
Person.prototype.name = "John";

let john = new Person();
console.log(john.name);  // 'John'
john.name = 'Carl';
console.log(john.name);  // 'Carl'
delete john.name;
console.log(john.name);  // 'John'
delete john.name;
console.log(john.name);  // 'John'
delete john.__proto__.name;
console.log(john.name);  // undefined

Zde můžeme vidět, že při přímém nastavení vlastnosti se tato stává přímým potomkem objektu, přestože existuje ve svém prototypu. Také k odstranění vlastnosti definované v prototypu nedojde, dokud ji nesmažeme po přístupu k objektu v jeho prototypu.

  • Aktualizace vlastnosti typu reference (prostřednictvím metod prototypu) definované v prototypu konstruktoru ji upraví pro všechny jeho instance
function Person(){}
Person.prototype.friends = ['James','Jaden']
let john = new Person(),
    joe = new Person();

console.log(john.fields); // ['James','Jaden']
console.log(joe.fields); // ['James','Jaden']
john.friends.splice(1,0,'Jenny','Joseph');
console.log(john.friends); // ['James','Jenny','Joseph','Jaden'];
console.log(joe.friends); // ['James','Jenny','Joseph','Jaden'];

Doufám, že příklad je ze samotného názvu jasný. :D.

Podle posledních dvou klíčových bodů můžeme dojít k závěru, že

Proces nastavení nebo odstranění vlastnosti se provádí v rámci rozsahu vlastnosti, zatímco proces získání nebo aktualizace vlastnosti spadá do rozsahu prototypu.

Dědičnost pomocí prototypů

Nyní vytvoříme dva konstruktory, z nichž druhý bude zděděn od prvního. I když možná znáte jeho třídní způsob dědičnosti v ES6 pomocí extends a super ale udělejme to nyní prototypovým způsobem založeným na našem dosavadním chápání.

Nejprve vytvořte nezávislé konstruktory

function Person(name,age){
  this.name = name;
  this.age = age;
}

/**
* Defining function in prototype as it
* is a common functionality shared across 
* all the instances
*/
Person.prototype.greet = function(){
  console.log(`Hi, I am ${this.name} and my age is ${this.age}.`);
}

function Adult(name,age,occupation){
  this.name = name;
  this.age = age;
  this.occupation = occupation;
}

Adult.prototype.greetAsAdult = function(){
  console.log(`Hi, I am ${this.name}, my age is ${this.age} and I work as a ${this.occupation}.`);
}

Zde vidíme, že Adult lze zdědit z Person konstruktor vzhledem k tomu, že chceme greet funkce být součástí konstruktoru.

Jak to uděláme?

  • Můžeme zavolat Person konstruktor používající rozsah Human . Můžeme použít .call metoda dostupná na prototypu Function konstruktor.
  • Musíme nastavit prototype z Human na hodnotu Person a konstruktér sám sobě.

Nejprve to udělejme pro Adult třída.

function Adult(name,age,occupation){
  Person.call(this,name,age);
  this.occupation = occupation;
}

// setting the basic properties of a prototype
Adult.prototype = Object.create(Person.prototype);
Adult.prototype.constructor = Adult;

Pomocí Object.create , vytvoříme prázdný objekt s vlastnostmi prototypu Person.prototype . Je to proto, že později můžeme definovat naše metody na Adult.prototype aniž byste se dotkli předmětu prototypu základního konstruktoru.

Jakmile jsme toho udělali hodně, nyní můžeme definovat vlastnosti na Adult prototyp.

Adult.prototype.greetAsAdult = function(){
  console.log(`Hi, I am ${this.name}, my age is ${this.age} and I work as a ${this.occupation}.`);
}

Nyní vytvoříme instanci Adult a zavolejte funkce.

let john = new Adult("John",23,"Software Developer");
john.greet(); // Hi, I am John and my age is 23.
john.greetAsAdult(); // Hi, I am John, my age is 23 and I work as a Software Developer.

Závěr

  • Prototypy jsou objekty existující na každé deklaraci funkce existující v kódu JS.
  • Tvoří řetězec prototypů se svými nadřazenými prototypy konstruktorů, dokud nebudou existovat.
  • Můžeme prohlásit, že společné funkce budou sdíleny napříč všemi instancemi těchto konstruktorů v rámci samotného prototypu.
  • Prototypy hrají hlavní roli při dědění vlastností základních konstruktorů.

Závěrečné poznámky

Pokud jste se dostali do této sekce, doufám, že jste článek prošli xD. Neváhejte příspěvek v diskuzích ocenit nebo kritizovat. Ukažte trochu lásky, pokud je to pro vás užitečné.

Chtěl bych napsat více příspěvků o konceptech Javascript, konceptech React a některých frontendových projektech, na kterých v současnosti pracuji.

Reference

  • https://betterprogramming.pub/prototypes-in-javascript-5bba2990e04b
  • https://github.com/leonardomso/33-js-concepts#17-prototype-inheritance-and-prototype-chain