Vezérlőpult
12. Osztályok · Modern JS

3. blokk · Modern JS · 12. fejezet

Osztályok

A 7. fejezetben objektumokat írtál kézzel: { nev: "Egér", ar: 6990 }. Amint tíz terméked van, és mindegyiknek ugyanúgy kell bruttó árat számolni, kell egy sablon: egy class leírja, milyen mezői és metódusai vannak minden terméknek, a new pedig annyi példányt gyárt belőle, amennyit akarsz. Ebben a fejezetben megtanulod a konstruktort, a metódusokat, a gettert/settert, a static-ot és az öröklést — mindazt, amire a Node-projektek service-rétege és hibaosztályai épülnek.

Élő gyakorló11 feladat automatikus ellenőrzéssel≈ 1,5 óra

Tanulási célok

1Miért kell osztály?

Egy webshopban sok termék van, és mindegyiknek ugyanaz az alakja (név, ár, készlet) és ugyanazok a műveletei (bruttó ár, raktáron van-e). Sima objektumokkal ezt kétféleképpen oldottuk meg eddig — és mindkettőnek van baja:

// 1) kézzel írt objektumok: a metódust minden darabba be kell másolni
const eger  = { nev: "Egér",  ar: 6990, brutto() { return Math.round(this.ar * 1.27); } };
const kabel = { nev: "Kábel", ar: 1990, brutto() { return Math.round(this.ar * 1.27); } };

// 2) külön függvény, amely objektumot kap: az adat és a művelet szétválik
function brutto(termek) { return Math.round(termek.ar * 1.27); }

Az osztály a kettő előnyét egyesíti: egy helyen írod le a mezőket és a műveleteket, és ebből a sablonból gyártod a példányokat. Az objektum, amit így kapsz, ugyanolyan objektum, mint eddig — pont-jelöléssel olvasod, tömbbe teheted, JSON-ba írhatod. Csak a létrehozása rendezettebb, és van „típusa": meg tudod kérdezni, hogy termék-e.

2class, constructor, this, new

Az osztályt a class kulcsszóval írjuk, a neve nagy kezdőbetűs. A constructor egy speciális függvény, amely a példány létrehozásakor fut le: a paramétereket mezőkbe menti a this-en keresztül.

class Termek {
  constructor(nev, ar, keszlet = 0) {
    this.nev = nev;          // mező: a példány saját adata
    this.ar = ar;
    this.keszlet = keszlet;
  }
}

const eger = new Termek("Egér", 6990, 3);   // példányosítás: new + az osztály neve
const kabel = new Termek("Kábel", 1990);    // keszlet = 0 (alapérték)

console.log(eger.nev);          // Egér
console.log(kabel.keszlet);     // 0
console.log(eger);              // Termek { nev: 'Egér', ar: 6990, keszlet: 3 }

Mi történik a new Termek(...) hatására? 1) létrejön egy üres objektum; 2) ez lesz a this a konstruktorban; 3) lefut a konstruktor, feltölti a mezőket; 4) az objektum a kifejezés értéke lesz — return nem kell. A this tehát mindig azt a példányt jelenti, amelyen éppen dolgozunk.

instanceof — melyik osztályból van?

console.log(eger instanceof Termek);   // true
console.log(typeof eger);              // object — a typeof nem tud az osztályról
console.log({ nev: "Egér" } instanceof Termek);   // false — sima objektum
⚠️ new NÉLKÜL NEM MEGY

Az osztályt csak new-val lehet meghívni: a Termek("Egér", 6990) TypeError-t dob („Class constructor Termek cannot be invoked without 'new'"). Ha egy mezőt elfelejtesz a konstruktorban this-re tenni, az csak lokális változó marad, és a példányon undefined lesz.

3Metódusok

A metódus egy függvény az osztály törzsében — function kulcsszó és vessző nélkül. A mezőket a this-en keresztül éri el, és a példányon hívjuk ponttal:

class Termek {
  constructor(nev, ar, keszlet = 0) {
    this.nev = nev; this.ar = ar; this.keszlet = keszlet;
  }
  brutto() {                                // metódus
    return Math.round(this.ar * 1.27);
  }
  raktaron() {
    return this.keszlet > 0;
  }
  elad(db) {                                // az állapotot módosító metódus
    this.keszlet -= db;
    return this;                            // this-t visszaadva láncolható
  }
  leiras() {
    return `${this.nev} - ${this.ar} Ft (${this.keszlet} db)`;
  }
}

const eger = new Termek("Egér", 6990, 3);
console.log(eger.brutto());      // 8877
console.log(eger.elad(1).elad(1).leiras());   // Egér - 6990 Ft (1 db)
console.log(eger.raktaron());    // true

Egy metódus nyugodtan hívhat másik metódust ugyanazon a példányon (this.brutto()) — így az összetett műveleteket kis, jól nevezett darabokból rakod össze.

ℹ️ A METÓDUS NEM SAJÁT TULAJDONSÁG

A mezők (nev, ar…) a példányon vannak, a metódusok viszont az osztályon, közösen minden példány számára. Ezért az Object.keys(eger) csak [ 'nev', 'ar', 'keszlet' ]-et ad, a JSON.stringify(eger)-ben sincs benne a brutto, és a { ...eger } másolat sima objektum lesz, metódusok nélkül. Ez így van jól: az adat utazik, a viselkedés az osztályban marad.

4Getter és setter

Néha egy érték számított (a bruttó ár az árból következik), mégis kényelmesebb mezőként olvasni, zárójel nélkül. Erre való a getter: a metódus elé get kerül, és úgy használod, mint egy tulajdonságot:

class Termek {
  constructor(nev, ar) { this.nev = nev; this.ar = ar; }

  get brutto() {                     // getter: számított „mező"
    return Math.round(this.ar * 1.27);
  }
  set brutto(b) {                    // setter: értékadáskor fut
    this.ar = Math.round(b / 1.27);
  }
}

const eger = new Termek("Egér", 6990);
console.log(eger.brutto);     // 8877 — nincs zárójel!
eger.brutto = 12700;          // a setter fut le
console.log(eger.ar);         // 10000

A setter tipikus haszna az ellenőrzés: nem engedsz negatív árat vagy érvénytelen értéket beállítani. Getter setter nélkül is lehet — akkor az érték csak olvasható. A getter nem jelenik meg a JSON.stringify-ban és az Object.keys-ben, mert nem a példány saját tulajdonsága.

✅ MIKOR GETTER, MIKOR METÓDUS?

Getter: ha az érték „úgy néz ki", mint egy tulajdonság — olcsó kiszámolni, nincs paramétere, nincs mellékhatása (brutto, teljesNev). Metódus: ha paraméter kell, vagy a hívás csinál valamit (elad(2), feldolgoz()).

5static — az osztályhoz tartozó metódus

A static metódus nem a példányon, hanem magán az osztályon hívható. Két tipikus feladata van: segédfüggvény, amelyhez nem kell példány, és „gyártó" metódus, amely kényelmesen épít példányt:

class Termek {
  constructor(nev, ar) { this.nev = nev; this.ar = ar; }

  static bruttobol(nev, brutto) {          // gyártó: bruttó árból készít példányt
    return new Termek(nev, Math.round(brutto / 1.27));
  }
  static olcsobb(a, b) {                   // segéd: két példányt hasonlít
    return a.ar <= b.ar ? a : b;
  }
}

const t = Termek.bruttobol("Egér", 8877);   // az osztályon hívjuk
console.log(t.ar);                            // 6990
console.log(typeof t.bruttobol);              // undefined — a példányon nincs

Ismerős a minta: a Math.round, az Array.isArray, az Object.keys, a JSON.parse mind ilyen „osztályon hívott" függvény. Statikus metódusban a this az osztályt jelenti, nem egy példányt — ezért benne nem érsz el mezőket.

6Öröklés: extends és super

Ha egy osztály egy másiknak a speciális esete („a vezető is alkalmazott, csak bónusszal"), nem írjuk újra a közös részt: az extends kulcsszóval örökli az ős mezőit és metódusait, és csak a különbséget adjuk hozzá.

class Alkalmazott {
  constructor(nev, ber) {
    this.nev = nev;
    this.ber = ber;
  }
  evesBer() { return this.ber * 12; }
  leiras()  { return `${this.nev}: ${this.evesBer()} Ft/év`; }
}

class Vezeto extends Alkalmazott {
  constructor(nev, ber, bonusz) {
    super(nev, ber);              // 1) az ős konstruktora — KÖTELEZŐ, és this előtt
    this.bonusz = bonusz;         // 2) a saját mező
  }
  evesBer() {                     // felülírás: ugyanaz a név, más a törzs
    return super.evesBer() + this.bonusz;   // super.metodus() = az ős változata
  }
}

const v = new Vezeto("Kata", 500000, 1000000);
console.log(v.evesBer());        // 7000000 — a felülírt változat
console.log(v.leiras());         // Kata: 7000000 Ft/év — az örökölt leiras() a felülírt evesBer()-t hívja
console.log(v instanceof Vezeto, v instanceof Alkalmazott);   // true true

Három szabály, amit érdemes megjegyezni:

⚠️ NE ÖRÖKÖLJ MINDENT

Az öröklés akkor jó, ha a „B egy A" mondat igaz (a vezető egy alkalmazott). Ha csak használ valamit (a kosár termékeket tartalmaz), az nem öröklés, hanem egy mező — this.tetelek = []. Mély öröklési láncokat a gyakorlatban ritkán írunk; egy-két szint bőven elég.

⚙️ HOL JÖN ELŐ NODE-BAN?

Három helyen biztosan találkozol vele. Saját hibaosztályok: class NotFoundError extends Error — a beépített Error-ból örökölsz, és a hibakezelő a típus alapján dönt (404 vagy 500) — ez a 13. fejezet témája. Service-osztályok: a TermekService egy helyen tartja az üzleti logikát (listaz(), letrehoz(adat)), a route-ok csak meghívják. Könyvtárak: az adatbázis-kliens (pl. a Prisma) osztálypéldány — const prisma = new PrismaClient() — és a metódusait hívod.

7toString és toJSON

Két metódusnév különleges: a JavaScript automatikusan hívja őket. A toString() akkor fut, amikor a példányt szöveggé kell alakítani (template literálban, + szöveggel, String(p)); a toJSON() eredményét pedig a JSON.stringify írja ki a példány helyett:

class Penz {
  constructor(osszeg, deviza = "HUF") {
    this.osszeg = osszeg;
    this.deviza = deviza;
  }
  toString() {
    return `${this.osszeg} ${this.deviza}`;
  }
  toJSON() {
    return { osszeg: this.osszeg, deviza: this.deviza, formazott: this.toString() };
  }
}

const p = new Penz(6990);
console.log(`Ár: ${p}`);            // Ár: 6990 HUF — a toString() hívódott
console.log(JSON.stringify(p));     // {"osszeg":6990,"deviza":"HUF","formazott":"6990 HUF"}

toJSON nélkül a JSON.stringify egyszerűen a saját mezőket írja ki — ez a legtöbbször elég. Akkor írsz toJSON-t, ha el akarsz rejteni egy mezőt (pl. jelszó-hash), vagy más néven/alakban akarod küldeni. A console.log(p) viszont egyiket sem használja: az a nyers példányt mutatja, az osztály nevével.

8Privát mező és két záró gondolat

Egy #-tel kezdődő mező privát: csak az osztály törzsében érhető el, kívülről nem olvasható és nem írható — így a példány állapotát csak a metódusokon keresztül lehet módosítani:

class Raktar {
  #keszlet = 0;                     // privát mező, kezdőértékkel (a konstruktoron kívül is állhat)

  bevetel(db) {
    if (db > 0) this.#keszlet += db;   // belül: this.#keszlet
  }
  get keszlet() {
    return this.#keszlet;            // kifelé csak olvasható
  }
}

const r = new Raktar();
r.bevetel(5);
r.bevetel(-3);                    // nem történik semmi
console.log(r.keszlet);           // 5
console.log(r);                   // Raktar {} — a #keszlet nem látszik, JSON-ba sem kerül
// r.#keszlet = 100;              → SyntaxError: kívülről tilos

Publikus mezőt is deklarálhatsz a konstruktoron kívül ugyanígy, csak # nélkül (tetelek = [];) — ez ugyanaz, mintha a konstruktorban this.tetelek = []-t írnál.

Osztály vagy gyári függvény?

Egy sima függvény, amely objektumot ad vissza (function ujTermek(nev, ar) { return { nev, ar, brutto() {…} }; }), ugyanezt tudja — instanceof és öröklés nélkül. Kis, egyszerű adatoknál ez is jó; osztályt akkor válassz, ha több példány közös viselkedését, típusát vagy öröklését akarod kifejezni.

„Prototípus-cukorka"

A class a színfalak mögött a JavaScript régi prototípus-mechanizmusát használja (ezért is tud a példány „nem saját" metódust hívni) — a class csak olvashatóbb szintaxis ugyanerre. Ennyit elég tudni róla: a mai kódban class-t írunk, nem prototype-ot.

9Élő kód — próbáld ki!

Írj osztályt, példányosítsd, hívd a metódusait. Nézd meg, hogyan írja ki a console.log a példányt (az osztály nevével), és mit hagy ki a JSON.stringify. A kimenet úgy jelenik meg, ahogy Node-ban látnád.

JavaScript-gyakorló

Válassz mintát vagy írj sajátot — majd Futtatás (Ctrl+Enter) ↓

A kimenet itt jelenik meg…

10Feladatok

Ebben a fejezetben osztályokat írsz, nem függvényeket. Az ellenőrző az adott nevű osztályt new-val példányosítja a teszt argumentumaival, majd lépésenként metódusokat hív és tulajdonságokat olvas rajta — a teszt sora pontosan ezt mutatja, pl. new Kosar().hozzaad('Egér', 6990).osszeg(). A „VÁRT" érték az utolsó lépés eredménye; ha nincs lépés, a példány saját mezőit hasonlítja sima objektumként (a metódusok nem számítanak). Getternél zárójel nélküli olvasás áll (.brutto). Az osztály a kód legfelső szintjén legyen, és pontosan úgy hívd, ahogy a feladat kéri.

Gyakorló feladatok

⭐ alap · ⭐⭐ haladó · ⭐⭐⭐ kihívás — a haladásod ebben a böngészőben megmarad.

Kész: 0 / 0
✅ HA A TESZT „nem metódusa a példánynak" HIBÁT AD

Elgépelted a metódus nevét, vagy getterként írtad, amit metódusként kellett volna (vagy fordítva). Nézd meg a teszt sorát: .brutto zárójel nélkül = getter, .raktaron() zárójellel = metódus. A Futtatás gombbal a saját console.log-jaiddal is kipróbálhatod a példányt.

11Összegzés

  1. class = sablon: mezők + műveletek egy helyen; new Osztaly(...) példányt gyárt, a constructor a this-re teszi a mezőket; instanceof mondja meg a típust.
  2. Metódus: függvény az osztály törzsében, this-szel éri el a mezőket; nem saját tulajdonság — Object.keys és JSON.stringify nem mutatja. return this → láncolható.
  3. get / set: számított „mező" zárójel nélkül (t.brutto); a setter értékadáskor fut, ellenőrzésre jó.
  4. static: az osztályon hívjuk (Termek.bruttobol(...)), nem a példányon — segéd- és gyártó metódusok.
  5. extends örököl, a leszármazott konstruktorában super(...) kötelező a this előtt; felülírás = ugyanaz a metódusnév, super.metodus() az ős változata.
  6. toString() szöveggé alakításkor, toJSON() a JSON.stringify-ban hívódik automatikusan.
  7. #mezo privát: csak az osztályon belül érhető el. Osztály vs gyári függvény: közös viselkedés, típus, öröklés → osztály. A class a prototípusok olvasható szintaxisa.
➡️ KÖVETKEZŐ LÉPÉS

Az osztályok első komoly alkalmazása rögtön a következő fejezet: a hibakezelés. Megtanulod a try / catch-et, a throw-t, és extends Error-ral saját hibaosztályt írsz — pont úgy, ahogy egy Express-alkalmazás különbözteti meg a „nincs ilyen termék" és a „hibás adat" eseteket.