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 bizonyos adatokat és funkcionalitást tartalmazzanak. Az adattulajdonságokat az OOP világában mezőknek nevezzük, a függvénytulajdonságokat pedig metódusoknak.
A JavaScriptben egyáltalán nem voltak osztályok, mielőtt azok 2015-ben bekerültek a nyelvi specifikációba, de a prototípusalapú öröklés révén már addig is lehetővé tette az objektumorientált programozást.
És bár manapság már elérhető a class kulcsszó, a JavaScript továbbra is prototípusalapú nyelv.
Hogy megértsük, mit jelent ez a prototípusalapúság, és hogyan is működik valójában a JavaScript, menjünk vissza az időben oda, amikor még nem voltak osztályok.
A JavaScriptben a sablont (osztályt) egy hagyományos függvény valósítja meg.
Amikor egy függvényt ilyen sablonként szeretnénk használni, azt konstruktőrfüggvénynek nevezzük, és a konvenció szerint a függvény nevét nagybetűvel kell kezdeni.
A példányokat (objektumokat) a sablonból a new kulcsszóval hozzuk létre, amikor meghívjuk a konstruktőrfüggvényt.
function Car() {
// ...
}
const myCar = new Car();
const yourCar = new Car();
Fontos megjegyezni, hogy a JavaScriptben a példányok és a konstruktőrfüggvény a példányok létrehozása után is kapcsolatban maradnak egymással.
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 konstruktőrfü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.
Összefoglalva:
prototype tulajdonsága lesz a példány prototípusa.Gyakran szeretnéd, hogy minden származtatott objektum (példány) tartalmazzon bizonyos mezőket, és ezekhez kezdőértékeket adj át az objektum létrehozásakor.
Ezt a this kulcsszóval érheted el.
A konstruktőrfüggvényen belül a this azt az új objektumot képviseli, amely a new segítségével jön létre.
Amikor a konstruktőrfüggvényt a new kulcsszóval hívjuk meg, a this automatikusan visszaadódik belőle.
Ez azt jelenti, hogy új mezőket úgy adhatunk a létrejövő példányhoz, hogy hozzáadjuk őket a this-hez a konstruktőrfü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
A metódusokat a konstruktőrfü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ívtuk. 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 fenti példában a myCar egy hagyományos 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 megpróbálsz elérni egy objektum bármely tulajdonságát (mezőjét vagy metódusát), a JavaScript először azt ellenőrzi, hogy a kérdéses kulcs közvetlenül megtalálható-e magában az objektumban.
Ha nem, folytatja a keresést abban az objektumban, amelyre az eredeti objektum [[prototype]] tulajdonsága hivatkozik.
Ahogy korábban említettük, a példánkban a [[prototype]] a konstruktőrfüggvény prototype tulajdonságára mutat.
Itt találná 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 konstruktőrfü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().
Összefoglalva, meghívhatod a myCar.toString() metódust, és az létezni fog, mert a JavaScript az egész prototípusláncon végigkeresi azt a metódust.
Vedd figyelembe, hogy a prototípusláncon csak akkor haladunk végig, amikor egy értéket olvasunk ki. Egy tulajdonság közvetlen beállítása vagy egy példányobjektum egy tulajdonságának törlése csak azt a konkrét példányt érinti. Ez talán nem az, amire számítanál, ha egy osztályalapú öröklést használó nyelvhez vagy szokva.
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 cukrot nyújt, ami megkönnyíti az osztályok olvasását és írását.
Az új szintaxis jobban hasonlít ahhoz, ahogy az osztályokat például C++-ban vagy Javában írják.
Az ezekről a nyelvekről átváltó fejlesztőknek könnyebb az átállás.
Másrészt az osztályszintaxis utat nyit olyan új nyelvi funkcióknak, amelyek a prototípus szintaxisban nem érhetők el.
Az új szintaxissal az osztályokat a class kulcsszóval definiáljuk, amelyet az osztály neve, majd az osztály törzse követ kapcsos zárójelek között.
A törzs tartalmazza a konstruktőrfü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 konstruktőrfüggvény a prototípus szintaxisban.
Az osztály törzse az összes metódusdefiníciót is tartalmazza.
A metódusok szintaxisa hasonlít ahhoz a rövidített jelöléshez, amelyet akkor láttunk, amikor függvényeket adtunk értékként egy objektumon belül; 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
Tartsd észben, hogy a JavaScript a háttérben továbbra is prototípusalapú nyelv. Minden mechanizmus, amelyről a fenti „Prototípus szintaxis” részben tanultunk, továbbra is érvényes.
Alapértelmezés szerint a JavaScriptben minden példánymező nyilvános. Közvetlenül elérhetők, és értéket is adhatunk nekik.
Van azonban egy bevett konvenció, amely szerint az aláhúzással kezdődő mezőket és metódusokat privátként kell kezelni. Soha nem szabad őket 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ényével azonos nevű tulajdonságot kiolvasnak, vagy értéket adunk 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.
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 (hivatkozva: 2021. december 3.) ↩
Ebben a feladatban egy ablakkezelésre épülő számítógépes rendszert szimulálsz. Létrehozol néhány ablakot, amelyeket mozgatni és átméretezni lehet. A következő ábra szemlélteti azokat az értékeket, amelyekkel az alábbiakban dolgozni fogsz.
<--------------------- screenSize.width --------------------->
^ ┌────────────────────────────────────────────────────────────┐
| │ │
| │ position.x, _ │
| │ position.y \ │
| │ \<----- size.width -----> │
| │ ^ *──────────────────────┐ │
| │ | │ title │ │
| │ | ├──────────────────────┤ │
screenSize.height │ | │ │ │
| │ size.height │ │ │
| │ | │ contents │ │
| │ | │ │ │
| │ | │ │ │
| │ v └──────────────────────┘ │
| │ │
| │ │
v └────────────────────────────────────────────────────────────┘
📣 Hogy gyakorolhasd a JavaScript széles körű ismereteit, próbáld meg az 1. és 2. részfeladatot prototípus-szintaxissal, a többi részfeladatot pedig class szintaxissal megoldani.
Definiálj egy Size nevű osztályt (konstruktorfüggvényt).
Két mezője legyen, width és height, amelyek az ablak aktuális méreteit tárolják.
A konstruktorfüggvény fogadjon el kezdőértékeket ezekhez a mezőkhöz.
A szélességet első paraméterként, a magasságot pedig másodikként kapja.
Az alapértelmezett szélesség és magasság 80, illetve 60 legyen.
Ezenfelül definiálj egy resize(newWidth, newHeight) metódust, amely új szélességet és magasságot vesz át paraméterként, majd úgy módosítja a mezőket, hogy tükrözzék az új méretet.
const size = new Size(1080, 764);
size.width;
// => 1080
size.height;
// => 764
size.resize(1920, 1080);
size.width;
// => 1920
size.height;
// => 1080
Definiálj egy Position nevű osztályt (konstruktorfüggvényt) két mezővel, x és y, amelyek az ablak bal felső sarkának aktuális vízszintes és függőleges pozícióját tárolják.
A konstruktorfüggvény fogadjon el kezdőértékeket ezekhez a mezőkhöz.
Az x értékét első paraméterként, az y értékét pedig másodikként kapja.
Mindkét mező alapértelmezett értéke 0 legyen.
A (0, 0) pozíció a képernyő bal felső sarka; az x értéke jobbra haladva nő, az y értéke pedig lefelé haladva nő.
Definiálj egy move(newX, newY) metódust is, amely új x és y paramétereket vesz át, és úgy módosítja a tulajdonságokat, hogy tükrözzék az új pozíciót.
const point = new Position();
point.x;
// => 0
point.y;
// => 0
point.move(100, 200);
point.x;
// => 100
point.y;
// => 200
Definiálj egy ProgramWindow osztályt a következő mezőkkel:
screenSize: egy rögzített Size típusú értéket tárol, amelynek width értéke 800, height értéke pedig 600size: egy Size típusú értéket tárol, kezdőértéke a Size példány alapértelmezett értékeposition: egy Position típusú értéket tárol, kezdőértéke a Position példány alapértelmezett értékeAmikor az ablak megnyílik (létrejön), a kezdetekor mindig az alapértelmezett mérettel és pozícióval rendelkezik.
const programWindow = new ProgramWindow();
programWindow.screenSize.width;
// => 800
// Similar for the other fields.
Megjegyzés: a ProgramWindow nevet a Window helyett használjuk, hogy megkülönböztessük az osztályt a böngészőkörnyezetekben létező beépített Window osztálytól.
A ProgramWindow osztálynak tartalmaznia kell egy resize metódust.
Ez egy Size típusú paramétert vesz át bemenetként, és megpróbálja az ablakot a megadott méretre átméretezni.
Az új méret azonban nem léphet túl bizonyos korlátokat.
x = 400, y = 300, és egy height = 400, width = 300 méretre történő átméretezést kérsz, az ablak height = 300, width = 300 méretre lesz átméretezve, mivel a képernyő az y irányban nem elég nagy ahhoz, hogy a kérést teljesen befogadja.const programWindow = new ProgramWindow();
const newSize = new Size(600, 400);
programWindow.resize(newSize);
programWindow.size.width;
// => 600
programWindow.size.height;
// => 400
Az átméretezés funkción kívül a ProgramWindow osztálynak tartalmaznia kell egy move metódust is.
Ez egy Position típusú paramétert vesz át bemenetként.
A move metódus hasonlít a resize metódushoz, ez a metódus azonban az ablak pozícióját állítja be a kért értékre a méret helyett.
Ahogy a resize esetében, az új pozíció sem léphet túl bizonyos korlátokat.
x és y esetében is 0.x = 250, y = 100, és egy x = 600, y = 200 pozícióra történő mozgatást kérsz, az ablak x = 550, y = 200 pozícióra kerül, mivel a képernyő az x irányban nem elég nagy ahhoz, hogy a kérést teljesen befogadja.const programWindow = new ProgramWindow();
const newPosition = new Position(50, 100);
programWindow.move(newPosition);
programWindow.position.x;
// => 50
programWindow.position.y;
// => 100
Valósíts meg egy changeWindow függvényt, amely egy ProgramWindow példányt vesz át bemenetként, és az ablakot a megadott méretre és pozícióra változtatja.
A függvény a módosítások alkalmazása után adja vissza a kapott ProgramWindow példányt.
Az ablak szélessége 400, magassága 300 legyen, és az x = 100, y = 150 pozícióba kerüljön.
const programWindow = new ProgramWindow();
changeWindow(programWindow);
programWindow.size.width;
// => 400
// Similar for the other fields.
Iratkozz fel az Exercism-re, hogy megtanuld és elsajátítsd a(z) JavaScript nyelvet 37 fogalom159 feladat segítségével, valódi emberi mentorálással, mindez ingyen.