Pochopení prototypu JavaScriptu

JavaScript je prý jazyk založený na prototypech. Takže "prototypy" musí být důležitý pojem, ne?

Dnes vám vysvětlím, co jsou prototypy, co potřebujete vědět a jak prototypy efektivně používat.

Co jsou prototypy?

Za prvé nenechte se svést slovem „Prototyp“ . „Prototyp“ v JavaScriptu není totéž jako „prototyp“ v angličtině. Neznamená to počáteční verzi produktu, která byla rychle sestavena.

Místo toho je prototyp v JavaScriptu prostě slovo, které absolutně nic neznamená. Můžeme nahradit prototyp pomeranči a může to znamenat totéž.

Vzpomeňte si například na Apple. Než se Apple Computers staly populárními, pravděpodobně si představíte Apple jako ovoce červené barvy. „Apple“ v Apple Computers zpočátku nemá význam – ale nyní ano.

V případě JavaScriptu prototyp odkazuje na systém. Tento systém vám umožňuje definovat vlastnosti objektů, ke kterým lze přistupovat prostřednictvím instancí objektu.

:::Poznámka
Prototyp úzce souvisí s objektově orientovaným programováním. Nedávalo by to smysl, pokud nerozumíte, o čem je objektově orientované programování.

Než přejdete dále, doporučuji vám seznámit se s touto úvodní sérií o objektově orientovaném programování.
:::

Například Array je plán pro instance pole. Instanci pole vytvoříte pomocí [] nebo new Array() .

const array = ['one', 'two', 'three']
console.log(array)

// Same result as above
const array = new Array('one', 'two', 'three')

Pokud console.log toto pole, nevidíte žádné metody. Ale přesto můžete použít metody jako concat , slice , filter a map !

Proč?

Protože tyto metody jsou umístěny v prototypu pole. __proto__ můžete rozšířit objekt (Chrome Devtools) nebo <prototype> objekt (Firefox Devtools) a uvidíte seznam metod.



:::Poznámka
Oba __proto__ v Chrome a <prototype> ve Firefoxu ukazuje na objekt Prototype. Jen se v různých prohlížečích píší jinak.
:::

Když použijete map , JavaScript hledá map v objektu samotném. Pokud map nenalezen, JavaScript se pokouší vyhledat prototyp. Pokud JavaScript najde prototyp, pokračuje v hledání map v tomto prototypu.

Takže správná definice prototypu je:Objekt, ke kterému mají instance přístup když se snaží hledat nemovitost.

Prototypové řetězy

Když přistupujete ke službě, JavaScript dělá toto:

Krok 1 :JavaScript zkontroluje, zda je vlastnost dostupná uvnitř objektu. Pokud ano, JavaScript tuto vlastnost okamžitě použije.

Krok 2 :Pokud vlastnost NENÍ uvnitř objektu, JavaScript zkontroluje, zda je k dispozici prototyp. Pokud existuje prototyp, opakujte krok 1 (a zkontrolujte, zda je vlastnost uvnitř prototypu).

Krok 3 :Pokud nezbývají žádné další prototypy a JavaScript nemůže najít vlastnost, provede následující:

  • Vrátí undefined (pokud jste se pokusili o přístup ke službě).
  • Vyvolá chybu (pokud jste se pokusili volat metodu).

Schématicky tento proces vypadá takto:

Příklad řetězce prototypu

Řekněme, že máme Human třída. Máme také Developer Podtřída, která dědí z Human . Human s mají sayHello metoda a Developers mít code metoda.

Zde je kód pro Human

class Human {
  constructor(firstName, lastName) {
    this.firstName = firstName
    this.lastname = lastName
  }

  sayHello () {
    console.log(`Hi, I'm ${this.firstName}`)
  }
}

:::Poznámka
Human (a Developer níže) lze psát pomocí funkcí konstruktoru. Pokud použijeme funkce konstruktoru, prototype bude jasnější, ale vytváření podtříd bude obtížnější. Proto ukazuji příklad s Classes. (V tomto článku najdete 4 různé způsoby použití objektově orientovaného programování).

Zde je návod, jak byste napsali Human pokud jste místo toho použili konstruktor.

function Human (firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}

Human.prototype.sayHello = function () {
  console.log(`Hi, I'm ${this.firstName}`)
}

:::

Zde je kód pro Developer .

class Developer extends Human {
  code (thing) {
    console.log(`${this.firstName} coded ${thing}`)
  }
}

A Developer instance může použít obě code a sayHello protože tyto metody jsou umístěny v řetězci prototypů instance.

const zell = new Developer('Zell', 'Liew')
zell.sayHello() // Hi, I'm Zell
zell.code('website') // Zell coded website

Pokud console.log instance, můžete vidět metody v řetězci prototypů.

Prototypové delegování / Prototypová dědičnost

Prototypální delegování a Prototypální dědění znamenají totéž.

Jednoduše říkají, že používáme prototypový systém – kde vkládáme vlastnosti a metody do prototype objekt.

Měli bychom použít Prototypal Delegation?

Protože JavaScript je jazyk založený na prototypech, měli bychom používat Prototypal Delegation. Správně?

Vlastně ne.

Řekl bych, že to závisí na tom, jak píšete objektově orientované programování. Prototypy má smysl používat, pokud používáte třídy, protože jsou pohodlnější.

class Blueprint {
  method1 () {/* ... */}
  method2 () {/* ... */}
  method3 () {/* ... */}
}

Ale dává smysl NEPOUŽÍVAT prototypy, pokud používáte funkce Factory.

function Blueprint {
  return {
      method1 () {/* ... */}
      method2 () {/* ... */}
      method3 () {/* ... */}
  }
}

Znovu si přečtěte tento článek o čtyřech různých způsobech psaní objektově orientovaného programování.

Výkonnostní důsledky

Na výkonu mezi těmito dvěma metodami příliš nezáleží – pokud vaše aplikace nevyžaduje miliony operací. V této části se podělím o několik experimentů, které tento bod dokážou.

Nastavení

Můžeme použít performance.now pro přihlášení časového razítka před spuštěním jakékoli operace. Po spuštění operací použijeme performance.now pro opětovné přihlášení časového razítka.

Poté získáme rozdíl v časových razítkách, abychom změřili, jak dlouho operace trvaly.

const start = performance.now()
// Do stuff
const end = performance.now()

const elapsed = end - start
console.log(elapsed)

Použil jsem perf funkce, která mi pomůže s mými testy:

function perf (message, callback, loops = 1) {
  const startTime = performance.now()
  for (let index = 0; index <= loops; index++) {
    callback()
  }
  const elapsed = performance.now() - startTime
  console.log(message + ':', elapsed)
}

Poznámka:Můžete se dozvědět více o performance.now v tomto článku.

Experiment č. 1:Používání prototypů vs. nepoužívání prototypů

Nejprve jsem testoval, jak dlouho trvá přístup k metodě prostřednictvím prototypu oproti jiné metodě, která se nachází v samotném objektu.

Zde je kód:

class Blueprint () {
  constructor () {
    this.inObject = function () { return 1 + 1 }
  }

  inPrototype () { return 1 + 1 }
}

const count = 1000000
const instance = new Blueprint()
perf('In Object', _ => { instance.inObject() }, count)
perf('In Prototype', _ => { instance.inPrototype() }, count)

Průměrné výsledky jsou v této tabulce shrnuty takto:

Test 1 000 000 operací 10 000 000 operací
V objektu 3 ms 15 ms
V prototypu 2 ms 12 ms

Poznámka:Výsledky jsou z Devtools Firefoxu. Přečtěte si toto, abyste pochopili, proč srovnávám pouze s Firefoxem.

Verdikt:Nezáleží na tom, zda používáte prototypy nebo ne. Pokud neprovedete> 1 milion operací, nezmění se to.

Experiment č. 2:Třídy vs tovární funkce

Tento test jsem musel spustit, protože doporučuji používat prototypy, když používáte třídy, a nepoužívat prototypy, když používáte funkce Factory.

Potřeboval jsem otestovat, zda je vytváření funkcí Factory výrazně pomalejší než vytváření tříd.

Tady je kód.

// Class blueprint
class HumanClass {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }

  sayHello () {
    console.lg(`Hi, I'm ${this.firstName}}`)
  }
}

// Factory blueprint
function HumanFactory (firstName, lastName) {
  return {
    firstName,
    lastName,
    sayHello () {
        console.log(`Hi, I'm ${this.firstName}}`)
      }
  }
}

// Tests
const count = 1000000
perf('Class', _ => { new HumanClass('Zell', 'Liew') }, count)
perf('Factory', _ => { HumanFactory('Zell', 'Liew') }, count)

Průměrné výsledky jsou shrnuty v tabulce takto:

Test 1 000 000 operací 10 000 000 operací
Třída 5 ms 18 ms
Továrna 6 ms 18 ms

Verdikt:Nezáleží na tom, zda používáte funkce Class nebo Factory. Nebude to mít žádný rozdíl, i když spustíte> 1 milion operací.

Závěr o testech výkonu

Můžete použít funkce Classes nebo Factory. Můžete se rozhodnout používat prototypy, nebo ne. Je to opravdu na vás.

O výkon si nemusíte dělat starosti.

Děkuji za přečtení. Tento článek byl původně zveřejněn na mém blogu. Přihlaste se k odběru mého newsletteru, pokud chcete další články, které vám pomohou stát se lepším vývojářem frontendu.