Kurzusok
/
JavaScript
JavaScript
/
Tanterv
/
Prototípusok és osztályok
Pr

Prototípusok és osztályok ebben a kurzusban: JavaScript

32 feladat

A(z) Prototípusok és osztályok fogalomról

A JavaScript támogatja az objektumorientált programozást (OOP). Az OOP során objektumokat (példányokat) hozol létre „sablonokból” (osztályokból), hogy azok meghatározott adatokat és funkciókat tartalmazzanak. Az adatokat tároló tulajdonságokat az OOP szóhasználatában mezőknek, a függvényeket tároló tulajdonságokat metódusoknak nevezzük.

A JavaScriptnek egyáltalán nem voltak osztályai, mielőtt azok 2015-ben bekerültek a nyelvi specifikációba, de a prototípusalapú öröklődés segítségével már addig is lehetővé tette az objektumorientált programozást. És bár manapság már létezik a class kulcsszó, a JavaScript továbbra is prototípusalapú nyelv.

Hogy megértsük, mit jelent az, hogy egy nyelv prototípusalapú, és hogyan is működik valójában a JavaScript, térjünk vissza abba az időbe, amikor még nem voltak osztályok.

Prototípus szintaxis

Konstruktorfüggvény

A JavaScriptben a sablont (az osztályt) egy egyszerű függvény valósítja meg. Amikor egy függvényt ilyen sablonként szeretnénk használni, azt konstruktorfüggvénynek nevezzük, és a szokás az, hogy a függvény neve nagybetűvel kezdődik. A példányokat (objektumokat) a new kulcsszóval hozzuk létre a sablonból a konstruktorfüggvény meghívásakor.

function Car() {
  // ...
}

const myCar = new Car();
const yourCar = new Car();

Fontos megjegyezni, hogy a JavaScriptben a példányok és a konstruktorfüggvény kapcsolatban maradnak egymással akkor is, miután a példányok létrejöttek. Minden példányobjektum tartalmaz egy rejtett, belső tulajdonságot, amelyre a nyelvi specifikáció [[prototype]] néven hivatkozik. Ez hivatkozást tárol a konstruktorfüggvény prototype kulcsának értékére. Igen, jól olvastad: egy JavaScript-függvénynek lehetnek kulcs/érték párjai, mert a háttérben maga is objektum.

2015 óta a [[prototype]] a Object.getPrototypeOf() segítségével érhető el. Az előtt sok környezetben a __proto__ kulcson keresztül volt elérhető.

Ne keverd össze egy objektum prototípusát ([[prototype]]) a konstruktorfüggvény prototype tulajdonságával.

Note

Összefoglalva:

  • A konstruktorok a JavaScriptben egyszerű függvények.
  • Egy új példány létrehozásakor olyan objektum jön létre, amely kapcsolatban áll a konstruktorával; ezt a kapcsolatot nevezzük prototípusnak.
  • A függvények objektumok (meghívható objektumok), ezért lehetnek tulajdonságaik.
  • A konstruktor(függvény) prototype tulajdonsága lesz a példány prototípusa.

Példánymezők

Gyakran szeretnéd, hogy a származtatott objektumok (példányok) mindegyike tartalmazzon néhány mezőt, és ezekhez kezdőértékeket adj át az objektum létrehozásakor. Ezt a this kulcsszóval valósíthatod meg. A konstruktorfüggvényen belül a this azt az új objektumot jelöli, amelyet a new fog létrehozni. A this automatikusan visszatér a konstruktorfüggvényből, amikor azt a new kulcsszóval hívjuk meg.

Ez azt jelenti, hogy úgy adhatunk mezőket az új példányhoz, hogy hozzáadjuk őket a this-hez a konstruktorfüggvényben.

function Car(color, weight) {
  this.color = color;
  this.weight = weight;
  this.engineRunning = false;
}

const myCar = new Car('red', '2mt');
myCar.color;
// => 'red'
myCar.engineRunning;
// => false

Példánymetódusok

A metódusokat a konstruktorfüggvény prototype tulajdonságán keresztül adjuk hozzá. Egy metóduson belül a this segítségével érheted el a példány mezőit. Ez az alábbi általános szabály miatt működik.

Amikor egy függvényt egy objektum metódusaként hívunk meg, a this értéke arra az objektumra áll be, amelyen a metódust meghívták. 1

function Car() {
  this.engineRunning = false;
  // ...
}

Car.prototype.startEngine = function () {
  this.engineRunning = true;
};

Car.prototype.addGas = function (litre) {
  // ...
};

const myCar = new Car();
myCar.startEngine();
myCar.engineRunning;
// => true

A prototípuslánc

A fenti példában szereplő myCar egy hétköznapi JavaScript-objektum, és ha megvizsgálnánk (például a böngésző konzoljában), nem találnánk közvetlenül a myCar objektumban egy startEngine tulajdonságot, amelynek értéke függvény. Akkor mégis hogyan működik a fenti kód?

A titok itt a prototípuslánc. Amikor egy objektum bármely tulajdonságát (mezőjét vagy metódusát) próbálod elérni, a JavaScript először megnézi, hogy a kérdéses kulcs megtalálható-e közvetlenül magában az objektumban. Ha nem, akkor tovább keresi a kulcsot abban az objektumban, amelyre az eredeti objektum [[prototype]] tulajdonsága hivatkozik. Ahogy korábban említettük, a példánkban a [[prototype]] a konstruktorfüggvény prototype tulajdonságára mutat. Itt találja meg a JavaScript a startEngine függvényt, mert oda adtuk hozzá.

function Car() {
  // ...
}

Car.prototype.startEngine = function () {
  // ...
};

És a lánc itt nem ér véget. A Car.prototype [[prototype]] tulajdonsága (myCar.[[prototype]].[[prototype]]) az Object.prototype-ra hivatkozik (az Object konstruktorfüggvény prototype tulajdonságára). Ez olyan általános metódusokat tartalmaz, amelyek minden JavaScript-objektum számára elérhetők, például a toString(). Az Object [[prototype]] tulajdonsága általában null, így a prototípuslánc ott véget ér. Összefoglalva: meghívhatod a myCar.toString() metódust, és az létezni fog, mert a JavaScript az egész prototípusláncon végigkeresi ezt a metódust. Részletes példát a »Inheritance and the prototype chain« című MDN-cikkben találsz.

Caution

Vedd figyelembe, hogy a prototípusláncot csak érték kiolvasásakor járjuk be. Egy példányobjektum tulajdonságának közvetlen beállítása vagy törlése csak arra a konkrét példányra hat. Ez talán nem az, amire egy osztályalapú öröklődést használó nyelvhez szokott programozó számítana.

Dinamikus metódusok (metódusok hozzáadása minden létező példányhoz)

A JavaScript lehetővé teszi, hogy metódusokat adj minden létező példányhoz, akkor is, ha azok már létrejöttek.

Megtudtuk, hogy minden példány hivatkozást tárol a konstruktorfüggvény prototype tulajdonságára. Ez azt jelenti, hogy ha bejegyzést adsz ahhoz a prototype objektumhoz, az új bejegyzés (például egy új metódus) azonnal elérhetővé válik a konstruktorfüggvényből létrehozott összes példány számára.

function Car() {
  this.engineRunning = false;
}

const myCar = new Car();
// Calling myCar.startEngine() here would result in "TypeError:
// myCar.startEngine is not a function".

Car.prototype.startEngine = function () {
  this.engineRunning = true;
};

myCar.startEngine();
// This works, even though myCar was created before the method
// was added.

Elméletben a dinamikus metódusokkal akár beépített objektumok, például az Object vagy az Array funkcióit is bővítheted a prototípusuk módosításával. Ezt monkey patchingnek nevezik. Mivel ez a változtatás az egész alkalmazásra hatással van, jobb elkerülni, hogy ne idézz elő nem szándékolt mellékhatásokat. Az egyetlen észszerű felhasználási eset az, amikor egy hiányzó metódushoz nyújtasz polyfillt régebbi környezetekben.

Osztályszintaxis

Manapság a JavaScript támogatja az osztályok definiálását a class kulcsszóval. Ez 2015-ben került be a nyelvi specifikációba. Egyrészt ez szintaktikai cukor, ami könnyebben olvashatóvá és írhatóvá teszi az osztályokat. Az új szintaxis jobban hasonlít ahhoz, ahogy az olyan nyelvekben, mint a C++ vagy a Java, az osztályokat írják. Az ezekről a nyelvekről átváltó fejlesztők könnyebben alkalmazkodnak. Másrészt az osztályszintaxis utat nyit olyan új nyelvi funkcióknak, amelyek a prototípus szintaxisban nem érhetők el.

Osztálydeklarációk

Az új szintaxisban az osztályokat a class kulcsszóval definiáljuk, amelyet az osztály neve, majd kapcsos zárójelek között az osztály törzse követ. A törzs tartalmazza a konstruktorfüggvény definícióját, azaz egy constructor nevű speciális metódust. Ez a függvény pontosan úgy működik, mint a konstruktorfüggvény a prototípus szintaxisban. Az osztály törzse ezen kívül az összes metódusdefiníciót is tartalmazza. A metódusok szintaxisa hasonlít ahhoz a rövidített jelöléshez, amit a függvények objektumon belüli értékként való hozzáadásánál láttunk; lásd az Objektumok fogalmat.

class Car {
  constructor(color, weight) {
    this.color = color;
    this.weight = weight;
    this.engineRunning = false;
  }

  startEngine() {
    this.engineRunning = true;
  }

  addGas(litre) {
    // ...
  }
}

const myCar = new Car();
myCar.startEngine();
myCar.engineRunning;
// => true

A függvénydeklarációkhoz és függvénykifejezésekhez hasonlóan a JavaScript a fent bemutatott osztálydeklaráció mellett osztálykifejezéseket is támogat.

Tartsd észben, hogy a JavaScript a háttérben továbbra is prototípusalapú nyelv. A fenti „Prototípus szintaxis” szakaszban megismert mechanizmusok mind érvényben vannak.

Privát mezők, getterek és setterek

A JavaScriptben alapértelmezés szerint minden példánymező nyilvános. Közvetlenül elérhetők, és értéket is adhatsz nekik.

A valódi privát mezők bekerülése a nyelvi specifikációba még folyamatban van; a részletekért lásd a javaslati dokumentumot.

Addig is támaszkodhatsz a bevált konvencióra: az aláhúzással kezdődő mezőket és metódusokat privátként kell kezelni. Ezeket soha nem szabad közvetlenül az osztályon kívülről elérni.

A privát mezőket néha getterek és setterek kísérik. A get és set kulcsszavakkal olyan függvényeket definiálhatsz, amelyek akkor futnak le, amikor egy, a függvénnyel azonos nevű tulajdonságot elérnek vagy értéket adnak neki.

class Car {
  constructor() {
    this._mileage = 0;
  }

  get mileage() {
    return this._mileage;
  }

  set mileage(value) {
    throw new Error(`Mileage cannot be manipulated, ${value} is ignored.`);
    // Just an example, usually you would not provide a setter in this case.
  }
}

const myCar = new Car();
myCar.mileage;
// => 0
myCar.mileage = 100;
// => Error: Mileage cannot be manipulated, 100 is ignored.

Osztálymezők és osztálymetódusok

Az OOP-ban néha olyan segédmezőket vagy segédmetódusokat szeretnél biztosítani, amelyek nem egy adott példánytól függenek. Ehelyett magára az osztályra vannak definiálva. Ezt a static kulcsszóval érheted el.

class Car {
  static type = 'vehicle';

  static isType(targetType) {
    return targetType === 'vehicle';
  }
}

Car.type;
// => 'vehicle'

Car.isType('road sign');
// => false

Osztályalapú öröklődés

A korábban látott, a prototípuslánc mentén történő öröklődés mellett a JavaScriptben az osztályok közötti öröklődést is kifejezheted. Ezt az Öröklődés fogalom tárgyalja.


  1. this példák - objektum metódusaként, MDN. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this#as_an_object_method (hozzáférés: 2021. december 3.) ↩

Szerkesztés GitHubon A hivatkozás új ablakban vagy lapon nyílik meg

Tanuld meg a(z) Prototípusok és osztályok fogalmat