3. blokk · Modern JS · 12. fejezet
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.
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.
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.
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
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.
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 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.
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.
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()).
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.
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:
super(...)-t kell hívni, csak utána használható a this.
Ha nem írsz konstruktort, az ősé fut le automatikusan ugyanazokkal a paraméterekkel.super.nev()-vel éred el.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.
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.
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.
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.
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.
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.
Í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.
Válassz mintát vagy írj sajátot — majd Futtatás (Ctrl+Enter) ↓
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.
⭐ alap · ⭐⭐ haladó · ⭐⭐⭐ kihívás — a haladásod ebben a böngészőben megmarad.
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.
new Osztaly(...) példányt gyárt, a constructor a this-re teszi a mezőket; instanceof mondja meg a típust.this-szel éri el a mezőket; nem saját tulajdonság — Object.keys és JSON.stringify nem mutatja. return this → láncolható.t.brutto); a setter értékadáskor fut, ellenőrzésre jó.Termek.bruttobol(...)), nem a példányon — segéd- és gyártó metódusok.super(...) kötelező a this előtt; felülírás = ugyanaz a metódusnév, super.metodus() az ős változata.toString() szöveggé alakításkor, toJSON() a JSON.stringify-ban hívódik automatikusan.#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.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.