Kurzusok
/
JavaScript
JavaScript
/
Feladatok
/
Ablakkezelő rendszer
Ablakkezelő rendszer

Ablakkezelő rendszer

Tanulófeladat

Bevezetés

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.

Prototípus szintaxis

Konstruktőrfüggvény

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.

Note

Összefoglalva:

  • A konstruktorok a JavaScriptben hagyományos függvények.
  • Egy új példány létrehozása olyan objektumot eredményez, amely kapcsolatban áll a konstruktorával; ezt a kapcsolatot prototípusnak hívjuk.
  • A függvények objektumok (meghívható objektumok), ezért lehetnek tulajdonságaik.
  • A konstruktőr (függvény) prototype tulajdonsága lesz a példány prototípusa.

Példánymezők

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

Példánymetódusok

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 prototípuslánc

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.

Caution

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.

Osztály szintaxis

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.

Osztálydeklarációk

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.

Privát mezők, getterek és setterek

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.

  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 (hivatkozva: 2021. december 3.) ↩

Utasítások

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.

1. Definiáld a Size-t az ablak méreteinek tárolására

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

2. Definiáld a Position-t egy ablak pozíciójának tárolására

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

3. Definiáld a ProgramWindow osztályt

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 600
  • size: egy Size típusú értéket tárol, kezdőértéke a Size példány alapértelmezett értéke
  • position: egy Position típusú értéket tárol, kezdőértéke a Position példány alapértelmezett értéke

Amikor 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.

4. Adj hozzá egy metódust az ablak átméretezéséhez

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.

  • A megengedett legkisebb magasság vagy szélesség 1. Az 1-nél kisebb kért magasságokat vagy szélességeket 1-re vágjuk le.
  • A legnagyobb magasság és szélesség az ablak aktuális pozíciójától függ, és az ablak szélei nem léphetnek túl a képernyő szélein. A korlátoknál nagyobb értékeket a lehető legnagyobb méretre vágjuk le. Például ha az ablak pozíciója 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

5. Adj hozzá egy metódust az ablak mozgatásához

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.

  • A legkisebb pozíció x és y esetében is 0.
  • A legnagyobb pozíció mindkét irányban az ablak aktuális méretétől függ. A szélek nem léphetnek túl a képernyő szélein. A korlátoknál nagyobb értékeket a lehető legnagyobb méretre vágjuk le. Például ha az ablak mérete 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

6. Módosíts egy programablakot

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.
Szerkesztés GitHubon A hivatkozás új ablakban vagy lapon nyílik meg
JavaScript Exercism

Készen állsz elkezdeni a(z) Ablakkezelő rendszer feladatot?

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.