Průvodce porozuměním třídám v JavaScriptu

Úvod

Když přemýšlíte o třídách a Objektově orientované programování jako paradigma není JavaScript pravděpodobně prvním jazykem, který vás napadne.

V této příručce se pokusíme posunout JavaScript dále v seznamu přidružení a probereme, jak aplikovat Objektově orientované principy při psaní kódu JavaScript. Stojí za zmínku, že některé funkce, kterými se budeme zabývat, jsou stále ve vývoji, ale většina je ve výrobě a plně funkční. Průvodce náležitě aktualizujeme, jakmile bude vydán.

Vzhledem k tomu, že JavaScript se většinou používá na webu, aplikace OOP na něj může být opravdu užitečná, řekněme, když získáváte data ze serveru (například kolekce z databáze MongoDB), kterou můžete vytvořit ve třídě s atributy, protože dělá práci s daty intuitivnější a jednodušší.

Co je objektově orientované programování (OOP)?

Než začneme, proberme si definici OOP a některé základní principy. Pokud jste již obeznámeni s těmito koncepty, můžete pokračovat a přeskočit k vytvoření třídy v JavaScriptu.

Třída a atributy

Řekněme, že máme opravdu jednoduchou třídu s názvem 03 který má dva atributy - 18 a 29 , což jsou oba struny. Toto je náš plán na výrobu předmětu. Objekt této třídy by měl atributy a hodnoty, řekněme 38 a 41 .

Abychom mohli vytvářet objekty jako je tento z konkrétní třídy, musí tato třída obsahovat metodu konstruktoru - nebo zkráceně konstruktér . Konstruktor je prakticky řečeno manuál o tom, jak vytvořit instanci objektu a přiřadit hodnoty . Nejběžnější praxí při vytváření konstruktoru je pojmenovat jej stejně jako třídu, ale nemusí to tak být.

Například pro naše 51 třídu, definovali bychom 64 konstruktor, který definuje jak atributům v rámci třídy přiřazujeme hodnoty při vytváření instance. Obvykle přijímá 78 argumenty používané jako hodnoty pro atributy:

class ProgrammingLanguage {
    // Attributes
    String name;
    String founder;
    
    // Constructor method
    ProgrammingLanguage(string passedName, string passedFounder){
       name = passedName;
       founder = passedFounder;
    }
}

Poznámka: I když je to podobné, nejedná se o kód JavaScript a slouží pro ilustrativní účely. Při vytváření třídy budeme používat JavaScript.

Poté, když vytváříme instanci této třídy, předáme některé argumenty konstruktoru, vyvoláním 89 objekt:

ProgrammingLanguage js = new ProgrammingLanguage("JavaScript", "Brendan Eich");

To by vytvořilo objekt js typu 90 s atributy 102 a 111 .

Metody getter a setter

V OOP je další sada klíčových metod – getters a setři . Jak název napovídá, getter metoda získává nějaké hodnoty, zatímco setter nastaví je.

V OOP se používají k načítání atributů z objektu, spíše než k přímému přístupu k nim, k jejich zapouzdření, provádění potenciálních kontrol atd. Setters se používají k nastavení atributů objektů na dané hodnoty – opět v zapouzdřeném a izolovaném způsobem.

Poznámka: Aby byl tento přístup skutečně omezen, jsou atributy obvykle nastaveny na 127 (nepřístupné mimo třídu), když daný jazyk podporuje modifikátory přístupu.

Může vám být například zabráněno, pokud chcete někomu nastavit věk na 130 prostřednictvím nastavení , což by nebylo možné vynutit, pokud byste měli povolen přímý přístup k atributům.

Setters lze použít buď k aktualizaci hodnoty, nebo k jejímu počátečnímu nastavení, pokud použijete prázdné konstruktor – tj. konstruktor, který zpočátku nenastavuje žádné hodnoty.

Konvence pro pojmenování getterů a setterů je, že by měly mít předponu 144 nebo 157 , následovaný atributem, se kterým se zabývají:

getName() {
    return name;
}

setName(newName) {
    name = newName;
}

toto Klíčové slovo

Třídy jsou sebevědomé . 161 klíčové slovo se používá k odkazování na tuto instanci v rámci třídy, jakmile je vytvořena instance. Klíčové slovo budete vždy používat pouze v rámci třídy, která odkazuje sama na sebe.

Například v konstruktoru z dřívějška jsme použili předané proměnné 170 a 188 , ale co kdyby to byly jen 194 a 206 co dává větší smysl?

Náš konstruktor by vypadal takto:

ProgrammingLanguage(String name, String founder) {
    name = name;
    founder = founder;
}

Takže, které 216 nastavujeme na které 226 ? Nastavujeme předávanou hodnotu atributu nebo naopak?

Zde je 237 klíčové slovo začíná:

ProgrammingLanguage(String name, String name) {
       this.name = name;
       this.founder = founder;
}

Nyní je zřejmé, že nastavujeme hodnotu atributu této třídy na hodnotu předanou z konstruktoru.

Stejná logika platí pro naše getry a nastavovače:

getName() {
	return this.name;
}

setName(name) {
   this.name = name;
}

Získáváme a nastavujeme název z této třídy .

Syntaxe atributů a konstruktorů a také konvence pro používání velkých písmen se jazyk od jazyka liší, ale hlavní principy OOP zůstávají stejné.

Vzhledem k tomu, jak jsou konstruktory, getry a settery standardizované, má většina dnešních IDE integrovanou zkratku pro vytvoření metody konstruktoru a také getterů a setterů. Vše, co musíte udělat, je definovat atributy a vygenerovat je pomocí příslušného zástupce ve vašem IDE.

Nyní, když jsme se více seznámili s koncepty OOP, můžeme se ponořit do OOP v JavaScriptu.

Vytvoření třídy v JavaScriptu

Poznámka: Jedním z rozdílů, které JavaScript přináší, je to, že při definování tříd – nemusíte explicitně uvádět, které atributy/pole má. Je mnohem poddajnější a objekty stejné třídy mohou mít různá pole pokud si to přeješ. Na druhou stranu, toto se nedoporučuje vzhledem k faktu, že je to v rozporu s principy OOP, a standardizovaná praxe je částečně vynucována tím, že máte konstruktor, ve kterém nastavujete všechny atributy (a tedy máte nějaký druh seznamu atributů).

V JavaScriptu existují dva způsoby, jak vytvořit třídu:pomocí deklarace třídy a pomocí výrazu třídy .

Pomocí deklarace třídy , prostřednictvím 244 klíčové slovo, můžeme definovat třídu a všechny její atributy a metody v následujících složených závorkách:

class Athlete {}

Ty mohou být definovány v příslušných souborech nebo v jiném souboru spolu s jiným kódem jako třída pohodlí.

Případně pomocí výrazů třídy (pojmenovaný nebo nepojmenovaný) vám umožňuje definovat a vytvořit je inline:

// Named
let Athelete = class Athlete{}
   
// Unnamed
let Athlete = class {}
   
// Retrieving the name attribute
console.log(Athlete.name);

Načítání atributu tímto způsobem se nedoporučuje, stejně jako ve skutečném duchu OOP – neměli bychom mít přímý přístup k atributům třídy.

Protože nemáme konstruktor, ani gettery a nastavovače, pojďme si je definovat.

Vytvoření konstruktoru, getterů a setterů v JavaScriptu

Další věc, kterou je třeba poznamenat, je, že JavaScript vynucuje jméno konstruktéra. Musí se jmenovat 252 . Toto je také místo, kde v podstatě definujete atributy své třídy, i když trochu implicitněji než v jazycích, jako je Java:

class Athlete{
	constructor(name, height, weight){
        this._name = name;
        this._height = height;
        this._weight = weight;
    }
}

const athlete = new Athlete("Michael Jordan", 198, 98);

Pokud byste chtěli definovat atributy předem, můžete ale je to nadbytečné vzhledem k povaze JavaScriptu, pokud se nepokoušíte vytvořit soukromé vlastnosti. V každém případě byste měli před názvy atributů uvést 264 .

Vzhledem k tomu, že JavaScript nepodporoval zapouzdření hned po vybalení, byl to způsob, jak uživatelům vaší třídy říci, neby přistupovat k atributům přímo. Pokud někdy před názvem atributu uvidíte podtržítko - udělejte sobě a tvůrci třídy laskavost a nepřistupujte k němu přímo.

Poznámka: Bylo to technicky možné k vytváření soukromých atributů v rámci tříd JavaScriptu, ale nebyl široce přijat ani používán - Douglas Crockford navrhl skrytí proměnných v uzávěrkách, aby se dosáhlo tohoto efektu.

Zdarma e-kniha:Git Essentials

Prohlédněte si našeho praktického průvodce učením Git s osvědčenými postupy, průmyslově uznávanými standardy a přiloženým cheat sheetem. Přestaňte používat příkazy Google Git a skutečně se naučte to!

Svůj záměr můžete dále anotovat pomocí 270 anotace označující, jakou úroveň přístupu chcete, aby atribut měl:

class Athlete {
    /** @access private */
   _name;
    
    constructor(name){
        this._name = name;
    }
    
    getName() {
        return this._name;
    }
    
    setName(name) {
        this._name = name;
    }
}

Poté můžete vytvořit instanci objektu a také získat a nastavit jeho atribut:

var athlete = new Athlete('Michael Jordan');
console.log(athlete.getName());

athlete.setName('Kobe Bryant');
console.log(athlete.getName());

Výsledkem je:

Michael Jordan
Kobe Bryant

Můžete však také přistupovat přímo k nemovitosti:

console.log(athlete._name); // Michael Jordan

Nastavení polí jako soukromých

Nakonec soukromá pole byly představeny a mají předponu 282 . Ve skutečnosti vynucují použití polí jako soukromých a nemohou být přístupný mimo třídu – pouze prostřednictvím metod, které jej odhalují:

class Athlete {
    /** @access private */
    #name;
    
    constructor(name){
        this.#name = name;
    }
    
    getName() {
        return this.#name;
    }
    
    setName(name) {
        this.#name = name;
    }
}

var athlete = new Athlete('Michael Jordan');
console.log(athlete.getName()); // Michael Jordan
console.log(athlete.#name); // SyntaxError: Private field '#name' must be declared in an enclosing class

Tímto způsobem je skutečně dosaženo zapouzdření, protože uživatelé mohou přistupovat k atributům pouze prostřednictvím prověřených metod, které mohou ověřit vrácené hodnoty nebo jim zabránit v nastavení neočekávaných hodnot, jako je přiřazení čísla namísto řetězce k 292 atribut.

Poznámka: Chcete-li označit atribut jako soukromý, musíte jej deklarovat před gettry a settery. Tato funkce je aktivní od roku 2018 (Babel 7.0+), ale v některých starších prostředích nemusí fungovat.

získat a nastavit Klíčová slova

Případně má JavaScript speciální sadu klíčových slov - 305 a 316 , které lze použít k výrobě getrů a setrů. Při použití se svazují určité atributy funkcí vyvolaných, když k nim chcete přistupovat.

Je obvyklé používat stejný název mezi atributem a metodami getter/setter vázanými 326 a 335 , bez prefix (bylo by to nadbytečné):

class Athlete {

	constructor(name) {
        this._name = name;
    }
	
    get name() {
	    return this._name;
    }
    
    set name(name){
        this._name = name;
    }
}

var athlete = new Athlete("Michael Jordan");

console.log(athlete.name); // Output: Michael Jordan

athlete.name = "Kobe Bryant";
console.log(athlete.name); // Output: Kobe Bryant

I když to tak může vypadat, nejsme přístup k 349 atribut přímo. Implicitně voláme 352 zkusením pro přístup k atributu, když je požadavek přesměrován na 361 metoda. Aby to bylo jasnější, upravme 378 tělo metody:

get name() {
    return "Name: " + this._name;
}

Nyní toto:

var athlete = new Athlete('Michael Jordan')
console.log(athlete.name);

Výsledky v:

Name: Michael Jordan

Poznámka: Další důvod, proč přidat podtržítko (384 ) k názvům atributů je, pokud budete tento přístup používat pro definování getterů a setterů. Pokud bychom měli použít pouze 395 jako atribut by to bylo nejednoznačné vzhledem ke skutečnosti, že 409 může také odkazovat na 414 .

Jakmile se pokusíme vytvořit instanci třídy, spustilo by to rekurzivní smyčku, která by zaplnila zásobník volání, dokud mu nedojde paměť:

class Athlete {
    constructor(name) {
        this.name = name;
    }
  
    get name() {
        return this.name;
	}
    
    set name(name) {
        this.name = name;
    }
}

var athlete = new Athlete('Michael Jordan');
console.log(athlete.name);

Výsledkem je:

script.js:12
        this.name = name;
                  ^

RangeError: Maximum call stack size exceeded

Používáte funkce Getter/Setter nebo klíčová slova?

Komunita je rozdělena ve výběru mezi těmito a někteří vývojáři preferují jeden před druhým. Neexistuje žádný jasný vítěz a oba přístupy podporují principy OOP tím, že umožňují zapouzdření a mohou vracet a nastavovat soukromé atributy.

Definování metod třídy

Některé metody jsme již definovali dříve, jmenovitě metody getter a setter. V podstatě stejným způsobem můžeme definovat další metody, které provádějí jiné úkoly.

Existují dva hlavní způsoby, jak definovat metody - in-class a mimo třídu .

Doposud jsme používali definice ve třídě:

class Athlete {
 // Constructor, getters, setters
 
    sayHello(){
        return "Hello, my name is " + this.name;
    }
}
console.log(athlete.sayHello()) // Hello, my name is Kobe Bryant

Alternativně můžete explicitně vytvořit funkci prostřednictvím deklarace funkce mimo třídu:

class Athlete {
    // Class code
}

athlete.sayHello = function(){
    return "Hello, my name is " + athlete.name;
}

var athlete = new Athlete("Kobe Bryant");
console.log(athlete.sayHello()) // Output: Hello, my name is Kobe Bryant

Pokud jde o JavaScript, oba tyto přístupy jsou stejné, takže si můžete vybrat, který vám bude lépe vyhovovat.

Dědičnost tříd v JavaScriptu

Klíčovým konceptem OOP je třídní dědičnost . podtřída (dětská třída) lze rozšířit z třídy a definovat nové vlastnosti a metody při dědění některé z jeho supertřídy (rodičovská třída).

425 může být 438 , 440 nebo 454 ale všechny tři jsou instancí 466 .

V JavaScriptu 474 klíčové slovo se používá k vytvoření podtřídy:

// Athlete class definition

class BasketballPlayer extends Athlete {
    constructor(name, height, weight, sport, teamName){
        super(name, height, weight);
		this._sport = sport;
        this._teamName = teamName;
    }
    
    get sport(){
        return this._sport;
    }
    
    get teamName(){
        return this._teamName;
    }
}

const bp = new BasketballPlayer("LeBron James", 208, 108, "Basketball", "Los Angeles Lakers");

Vytvořili jsme objekt 481 třída, která obsahuje atributy použité v 497 class a také dva nové atributy, 501 a 511 - specifické pro 526 třída.

Podobně jako 532 odkazuje na tuto třídu , 546 odkazuje na nadtřídu. Zavoláním na číslo 553 s argumenty voláme konstruktor nadtřídy a nastavujeme několik atributů, než nastavíme nové specifické pro 564 třída.

Když použijeme 572 klíčové slovo, zdědíme všechny metody a atributy, které jsou přítomné v nadtřídě - to znamená, že jsme zdědili 587 metoda, getry a settery a všechny atributy. Můžeme vytvořit novou metodu pomocí této metody a přidat k ní další, například takto:

class BasketballPlayer extends Athlete{
	// ... previous code
	
	fullIntroduction(){
		return this.sayHello() + " and I play " + this.sport + " in " + this.teamName;
	}
}

const bp = new BasketballPlayer("LeBron James", 208, 108, "Basketball", "Los Angeles Lakers");
console.log(bp.fullIntroduction());

Což bude mít za následek:

Hello, my name is LeBron James and I play Basketball in Los Angeles Lakers

Poznámka: Nedefinovali jsme 599 metoda v 608 třídy, ale stále k němu má přístup přes 611 . Jak to? Není to součástí 621 třída? To je. Ale 635 zdědil tuto metodu takže je to tak dobré, jak je definováno v 649 třída.

instance Operátor

654 Operátor se používá ke kontrole, zda je nějaký objekt instance určitá třída. Návratový typ je 662 :

var bp = new BasketballPlayer();
var athlete = new Athlete();

console.log(bp instanceof BasketballPlayer); // Output: true
console.log(bp instanceof Athlete); // Output: true

console.log(athlete instanceof Athlete); // Output: true
console.log(athlete instanceof BasketballPlayer); // Output: false

A 672 je 684 takže 697 je příkladem obojího. Na druhé straně 700 nemusí být 714 , tedy 722 je pouze instancí 734 . Pokud vytvoříme instanci 748 jako basketbalový hráč , například 757 , jsou instancí obou.

Závěr

V této příručce jsme se podívali na některé základní principy OOP a také na to, jak fungují třídy v JavaScriptu. JavaScript ještě není plně vhodný pro OOP, ale činí se kroky k dalšímu přizpůsobení funkčnosti.

Prozkoumali jsme definice tříd, atributy, getry, nastavovače, zapouzdření, metody tříd a dědičnost.