Διαδρομές
/
JavaScript
JavaScript
/
Ασκήσεις
/
Σύστημα Παραθύρων
Σύστημα Παραθύρων

Σύστημα Παραθύρων

Άσκηση εκμάθησης

Εισαγωγή

Η JavaScript περιλαμβάνει τις δυνατότητες για αντικειμενοστρεφή προγραμματισμό (OOP). Στον αντικειμενοστρεφή προγραμματισμό, θέλεις να δημιουργείς αντικείμενα (στιγμιότυπα) από "πρότυπα" (κλάσεις), ώστε αυτά να περιλαμβάνουν συγκεκριμένα δεδομένα και λειτουργικότητα. Οι ιδιότητες που περιέχουν δεδομένα ονομάζονται πεδία στο πλαίσιο του αντικειμενοστρεφούς προγραμματισμού, ενώ οι ιδιότητες που περιέχουν συναρτήσεις ονομάζονται μέθοδοι.

Η JavaScript δεν είχε καθόλου κλάσεις προτού αυτές προστεθούν στις προδιαγραφές της γλώσσας το 2015, αλλά επέτρεπε τον αντικειμενοστρεφή προγραμματισμό μέσω κληρονομικότητας βασισμένης σε πρωτότυπα. Και παρόλο που σήμερα υπάρχει διαθέσιμη η λέξη-κλειδί class, η JavaScript παραμένει μια γλώσσα βασισμένη σε πρωτότυπα.

Για να καταλάβεις τι σημαίνει να είναι μια γλώσσα βασισμένη σε πρωτότυπα και πώς λειτουργεί πραγματικά η JavaScript, ας γυρίσουμε πίσω στην εποχή που δεν υπήρχαν κλάσεις.

Σύνταξη με πρωτότυπα

Συνάρτηση κατασκευαστή

Στη JavaScript, το πρότυπο (η κλάση) υλοποιείται μέσω μιας συνηθισμένης συνάρτησης. Όταν μια συνάρτηση πρόκειται να χρησιμοποιηθεί ως τέτοιο πρότυπο, ονομάζεται συνάρτηση κατασκευαστή και η σύμβαση είναι το όνομα της συνάρτησης να ξεκινά με κεφαλαίο γράμμα. Τα στιγμιότυπα (αντικείμενα) παράγονται από το πρότυπο χρησιμοποιώντας τη λέξη-κλειδί new κατά την κλήση της συνάρτησης κατασκευαστή.

function Car() {
  // ...
}

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

Είναι σημαντικό να σημειώσεις ότι στη JavaScript τα στιγμιότυπα και η συνάρτηση κατασκευαστή διατηρούν μια σχέση μεταξύ τους ακόμα και αφού δημιουργηθούν τα στιγμιότυπα. Κάθε αντικείμενο στιγμιότυπο περιλαμβάνει μια κρυφή, εσωτερική ιδιότητα που αναφέρεται ως [[prototype]] στις προδιαγραφές της γλώσσας. Αυτή κρατά μια αναφορά στην τιμή του κλειδιού prototype της συνάρτησης κατασκευαστή. Ναι, το διάβασες σωστά, μια συνάρτηση JavaScript μπορεί να έχει ζεύγη κλειδιού/τιμής, επειδή είναι επίσης ένα αντικείμενο στα παρασκήνια.

Note

Συνοψίζοντας:

  • Οι κατασκευαστές στη JavaScript είναι συνηθισμένες συναρτήσεις.
  • Η κατασκευή ενός νέου στιγμιότυπου δημιουργεί ένα αντικείμενο με σχέση προς τον κατασκευαστή του, η οποία ονομάζεται πρωτότυπό του.
  • Οι συναρτήσεις είναι αντικείμενα (καλούμενα αντικείμενα) και επομένως μπορούν να έχουν ιδιότητες.
  • Η ιδιότητα prototype του (κατασκευαστή ως) συνάρτησης θα γίνει το πρωτότυπο του στιγμιότυπου.

Πεδία στιγμιοτύπων

Συχνά, θέλεις όλα τα παράγωγα αντικείμενα (στιγμιότυπα) να περιλαμβάνουν κάποια πεδία και να περνάς κάποιες αρχικές τιμές για αυτά όταν κατασκευάζεται το αντικείμενο. Αυτό μπορεί να γίνει μέσω της λέξης-κλειδί this. Μέσα στη συνάρτηση κατασκευαστή, το this αντιπροσωπεύει το νέο αντικείμενο που θα δημιουργηθεί μέσω του new. Το this επιστρέφεται αυτόματα από τη συνάρτηση κατασκευαστή όταν αυτή καλείται με το new.

Αυτό σημαίνει ότι μπορούμε να προσθέσουμε πεδία στο νέο στιγμιότυπο προσθέτοντάς τα στο this μέσα στη συνάρτηση κατασκευαστή.

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

Μέθοδοι στιγμιοτύπων

Οι μέθοδοι προστίθενται μέσω της ιδιότητας prototype της συνάρτησης κατασκευαστή. Μέσα σε μια μέθοδο, μπορείς να προσπελάσεις τα πεδία του στιγμιότυπου μέσω του this. Αυτό λειτουργεί χάρη στον ακόλουθο γενικό κανόνα.

Όταν μια συνάρτηση καλείται ως μέθοδος ενός αντικειμένου, το this της ορίζεται στο αντικείμενο στο οποίο καλείται η μέθοδος. 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

Η αλυσίδα των πρωτοτύπων

Το myCar στο παραπάνω παράδειγμα είναι ένα συνηθισμένο αντικείμενο JavaScript και αν το εξετάζαμε (π.χ. στην κονσόλα του περιηγητή), δεν θα βρίσκαμε μια ιδιότητα startEngine με συνάρτηση ως τιμή απευθείας μέσα στο αντικείμενο myCar. Πώς λειτουργεί τότε ο παραπάνω κώδικας;

Το μυστικό εδώ ονομάζεται αλυσίδα των πρωτοτύπων. Όταν προσπαθείς να προσπελάσεις οποιαδήποτε ιδιότητα (πεδίο ή μέθοδο) ενός αντικειμένου, η JavaScript ελέγχει πρώτα αν το αντίστοιχο κλειδί υπάρχει απευθείας στο ίδιο το αντικείμενο. Αν όχι, συνεχίζει να ψάχνει το κλειδί στο αντικείμενο στο οποίο παραπέμπει η ιδιότητα [[prototype]] του αρχικού αντικειμένου. Όπως αναφέρθηκε προηγουμένως, στο παράδειγμά μας το [[prototype]] δείχνει στην ιδιότητα prototype της συνάρτησης κατασκευαστή. Εκεί θα έβρισκε η JavaScript τη συνάρτηση startEngine, επειδή την προσθέσαμε εκεί.

function Car() {
  // ...
}

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

Και η αλυσίδα δεν τελειώνει εκεί. Η ιδιότητα [[prototype]] του Car.prototype (myCar.[[prototype]].[[prototype]]) παραπέμπει στο Object.prototype (την ιδιότητα prototype της συνάρτησης κατασκευαστή Object). Περιέχει γενικές μεθόδους που είναι διαθέσιμες σε όλα τα αντικείμενα JavaScript, π.χ. τη toString(). Συμπερασματικά, μπορείς να καλέσεις τη myCar.toString() και αυτή η μέθοδος θα υπάρχει, επειδή η JavaScript αναζητά αυτή τη μέθοδο σε ολόκληρη την αλυσίδα των πρωτοτύπων.

Caution

Σημείωσε ότι η αλυσίδα των πρωτοτύπων διανύεται μόνο κατά την ανάκτηση μιας τιμής. Ο ορισμός μιας ιδιότητας απευθείας ή η διαγραφή μιας ιδιότητας ενός αντικειμένου στιγμιότυπου επηρεάζει μόνο αυτό το συγκεκριμένο στιγμιότυπο. Αυτό ίσως να μην είναι αυτό που θα περίμενες αν είσαι συνηθισμένος σε μια γλώσσα με κληρονομικότητα βασισμένη σε κλάσεις.

Σύνταξη με κλάσεις

Σήμερα, η JavaScript υποστηρίζει τον ορισμό κλάσεων με τη λέξη-κλειδί class. Αυτό προστέθηκε στις προδιαγραφές της γλώσσας το 2015. Από τη μια πλευρά, αυτό παρέχει συντακτική ζάχαρη που κάνει τις κλάσεις ευκολότερες στην ανάγνωση και τη συγγραφή. Η νέα σύνταξη μοιάζει περισσότερο με τον τρόπο που γράφονται οι κλάσεις σε γλώσσες όπως η C++ ή η Java. Οι προγραμματιστές που έρχονται από αυτές τις γλώσσες προσαρμόζονται ευκολότερα. Από την άλλη πλευρά, η σύνταξη με κλάσεις ανοίγει τον δρόμο για νέα χαρακτηριστικά της γλώσσας που δεν είναι διαθέσιμα στη σύνταξη με πρωτότυπα.

Δηλώσεις κλάσεων

Με τη νέα σύνταξη, οι κλάσεις ορίζονται με τη λέξη-κλειδί class, ακολουθούμενη από το όνομα της κλάσης και το σώμα της κλάσης μέσα σε άγκιστρα. Το σώμα περιέχει τον ορισμό της συνάρτησης κατασκευαστή, δηλαδή μια ειδική μέθοδο με το όνομα constructor. Αυτή η συνάρτηση λειτουργεί ακριβώς όπως η συνάρτηση κατασκευαστή στη σύνταξη με πρωτότυπα. Το σώμα της κλάσης περιέχει επίσης όλους τους ορισμούς των μεθόδων. Η σύνταξη για τις μεθόδους είναι παρόμοια με τη συντομευμένη σημειογραφία που είδαμε για την προσθήκη συναρτήσεων ως τιμών μέσα σε ένα αντικείμενο, δες την έννοια Αντικείμενα.

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

Να θυμάσαι ότι στα παρασκήνια, η JavaScript παραμένει μια γλώσσα βασισμένη σε πρωτότυπα. Όλοι οι μηχανισμοί που μάθαμε στην παραπάνω ενότητα "Σύνταξη με πρωτότυπα" εξακολουθούν να ισχύουν.

Ιδιωτικά πεδία, getters και setters

Από προεπιλογή, όλα τα πεδία στιγμιοτύπων είναι δημόσια στη JavaScript. Μπορούν να προσπελαστούν απευθείας και να τους ανατεθούν τιμές.

Ωστόσο, υπάρχει μια καθιερωμένη σύμβαση ότι τα πεδία και οι μέθοδοι που ξεκινούν με κάτω παύλα πρέπει να αντιμετωπίζονται ως ιδιωτικά. Δεν πρέπει ποτέ να προσπελαύνονται απευθείας από έξω από την κλάση.

Τα ιδιωτικά πεδία συνοδεύονται μερικές φορές από getters και setters. Με τις λέξεις-κλειδιά get και set μπορείς να ορίσεις συναρτήσεις που εκτελούνται όταν προσπελαύνεται ή όταν ανατίθεται μια ιδιότητα με το ίδιο όνομα με τη συνάρτηση.

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 - Ως μέθοδος αντικειμένου, MDN. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this#as_an_object_method (αναφορά: 3 Δεκεμβρίου 2021) ↩

Οδηγίες

Σε αυτή την άσκηση, θα προσομοιώσεις ένα σύστημα υπολογιστή βασισμένο σε παράθυρα. Θα δημιουργήσεις μερικά παράθυρα που μπορούν να μετακινηθούν και να αλλάξουν μέγεθος. Η παρακάτω εικόνα είναι αντιπροσωπευτική των τιμών με τις οποίες θα δουλέψεις παρακάτω.

                  <--------------------- screenSize.width --------------------->

       ^          ┌────────────────────────────────────────────────────────────┐
       |          │                                                            │
       |          │         position.x, _                                      │
       |          │         position.y   \                                     │
       |          │                       \<----- size.width ----->            │
       |          │                 ^      *──────────────────────┐            │
       |          │                 |      │        title         │            │
       |          │                 |      ├──────────────────────┤            │
screenSize.height │                 |      │                      │            │
       |          │            size.height │                      │            │
       |          │                 |      │       contents       │            │
       |          │                 |      │                      │            │
       |          │                 |      │                      │            │
       |          │                 v      └──────────────────────┘            │
       |          │                                                            │
       |          │                                                            │
       v          └────────────────────────────────────────────────────────────┘

📣 Για να εξασκήσεις το ευρύ φάσμα των δεξιοτήτων σου στη JavaScript, προσπάθησε να λύσεις τις εργασίες 1 και 2 με σύνταξη prototype και τις υπόλοιπες εργασίες με σύνταξη κλάσης.

1. Όρισε τη Size για την αποθήκευση των διαστάσεων του παραθύρου

Όρισε μια κλάση (συνάρτηση κατασκευαστή) με το όνομα Size. Θα πρέπει να έχει δύο πεδία, τα width και height, που αποθηκεύουν τις τρέχουσες διαστάσεις του παραθύρου. Η συνάρτηση κατασκευαστή θα πρέπει να δέχεται αρχικές τιμές για αυτά τα πεδία. Το width δίνεται ως πρώτη παράμετρος και το height ως δεύτερη. Οι προεπιλεγμένες τιμές για το width και το height θα πρέπει να είναι 80 και 60, αντίστοιχα.

Επιπλέον, όρισε μια μέθοδο resize(newWidth, newHeight) που παίρνει ως παραμέτρους ένα νέο πλάτος και ύψος και αλλάζει τα πεδία ώστε να αντικατοπτρίζουν το νέο μέγεθος.

const size = new Size(1080, 764);
size.width;
// => 1080
size.height;
// => 764

size.resize(1920, 1080);
size.width;
// => 1920
size.height;
// => 1080

2. Όρισε τη Position για την αποθήκευση της θέσης ενός παραθύρου

Όρισε μια κλάση (συνάρτηση κατασκευαστή) με το όνομα Position και δύο πεδία, τα x και y, που αποθηκεύουν την τρέχουσα οριζόντια και κατακόρυφη θέση, αντίστοιχα, της πάνω αριστερής γωνίας του παραθύρου. Η συνάρτηση κατασκευαστή θα πρέπει να δέχεται αρχικές τιμές για αυτά τα πεδία. Η τιμή για το x δίνεται ως πρώτη παράμετρος και η τιμή για το y ως δεύτερη. Η προεπιλεγμένη τιμή θα πρέπει να είναι 0 και για τα δύο πεδία.

Η θέση (0, 0) είναι η πάνω αριστερή γωνία της οθόνης, με τις τιμές του x να μεγαλώνουν καθώς κινείσαι προς τα δεξιά και τις τιμές του y να μεγαλώνουν καθώς κινείσαι προς τα κάτω.

Όρισε επίσης μια μέθοδο move(newX, newY) που παίρνει νέες παραμέτρους x και y και αλλάζει τις ιδιότητες ώστε να αντικατοπτρίζουν τη νέα θέση.

const point = new Position();
point.x;
// => 0
point.y;
// => 0

point.move(100, 200);
point.x;
// => 100
point.y;
// => 200

3. Όρισε μια κλάση ProgramWindow

Όρισε μια κλάση ProgramWindow με τα ακόλουθα πεδία:

  • screenSize: περιέχει μια σταθερή τιμή τύπου Size με width 800 και height 600
  • size : περιέχει μια τιμή τύπου Size· η αρχική τιμή είναι η προεπιλεγμένη τιμή του στιγμιοτύπου Size
  • position : περιέχει μια τιμή τύπου Position· η αρχική τιμή είναι η προεπιλεγμένη τιμή του στιγμιοτύπου Position

Όταν το παράθυρο ανοίγει (δημιουργείται), έχει πάντα το προεπιλεγμένο μέγεθος και την προεπιλεγμένη θέση στην αρχή.

const programWindow = new ProgramWindow();
programWindow.screenSize.width;
// => 800

// Similar for the other fields.

Σημείωση: Το όνομα ProgramWindow χρησιμοποιείται αντί για Window για να ξεχωρίζει η κλάση από την ενσωματωμένη κλάση Window που υπάρχει σε περιβάλλοντα browser.

4. Πρόσθεσε μια μέθοδο για αλλαγή μεγέθους του παραθύρου

Η κλάση ProgramWindow θα πρέπει να περιλαμβάνει μια μέθοδο resize. Θα πρέπει να δέχεται ως είσοδο μια παράμετρο τύπου Size και να επιχειρεί να αλλάξει το μέγεθος του παραθύρου στο καθορισμένο μέγεθος.

Ωστόσο, το νέο μέγεθος δεν μπορεί να ξεπερνά ορισμένα όρια.

  • Το ελάχιστο επιτρεπόμενο ύψος ή πλάτος είναι 1. Ζητούμενα ύψη ή πλάτη μικρότερα του 1 θα περικόπτονται στο 1.
  • Το μέγιστο ύψος και πλάτος εξαρτώνται από την τρέχουσα θέση του παραθύρου· οι άκρες του παραθύρου δεν μπορούν να ξεπεράσουν τις άκρες της οθόνης. Τιμές μεγαλύτερες από αυτά τα όρια θα περικόπτονται στο μεγαλύτερο μέγεθος που μπορούν να πάρουν. Για παράδειγμα, αν η θέση του παραθύρου είναι x = 400, y = 300 και ζητηθεί αλλαγή μεγέθους σε height = 400, width = 300, τότε το παράθυρο θα αλλάξει μέγεθος σε height = 300, width = 300, καθώς η οθόνη δεν είναι αρκετά μεγάλη στην κατεύθυνση y για να χωρέσει πλήρως το αίτημα.
const programWindow = new ProgramWindow();

const newSize = new Size(600, 400);
programWindow.resize(newSize);
programWindow.size.width;
// => 600
programWindow.size.height;
// => 400

5. Πρόσθεσε μια μέθοδο για μετακίνηση του παραθύρου

Εκτός από τη λειτουργία αλλαγής μεγέθους, η κλάση ProgramWindow θα πρέπει να περιλαμβάνει και μια μέθοδο move. Θα πρέπει να δέχεται ως είσοδο μια παράμετρο τύπου Position. Η μέθοδος move είναι παρόμοια με τη resize, ωστόσο αυτή η μέθοδος προσαρμόζει τη θέση του παραθύρου στη ζητούμενη τιμή, αντί για το μέγεθος.

Όπως και με τη resize, η νέα θέση δεν μπορεί να ξεπερνά ορισμένα όρια.

  • Η μικρότερη θέση είναι 0 και για το x και για το y.
  • Η μέγιστη θέση σε κάθε κατεύθυνση εξαρτάται από το τρέχον μέγεθος του παραθύρου. Οι άκρες δεν μπορούν να ξεπεράσουν τις άκρες της οθόνης. Τιμές μεγαλύτερες από αυτά τα όρια θα περικόπτονται στο μεγαλύτερο μέγεθος που μπορούν να πάρουν. Για παράδειγμα, αν το μέγεθος του παραθύρου είναι x = 250, y = 100 και ζητηθεί μετακίνηση σε x = 600, y = 200, τότε το παράθυρο θα μετακινηθεί σε x = 550, y = 200, καθώς η οθόνη δεν είναι αρκετά μεγάλη στην κατεύθυνση x για να χωρέσει πλήρως το αίτημα.
const programWindow = new ProgramWindow();

const newPosition = new Position(50, 100);
programWindow.move(newPosition);
programWindow.position.x;
// => 50
programWindow.position.y;
// => 100

6. Άλλαξε ένα παράθυρο προγράμματος

Υλοποίησε μια συνάρτηση changeWindow που δέχεται ως είσοδο ένα στιγμιότυπο ProgramWindow και αλλάζει το παράθυρο στο καθορισμένο μέγεθος και την καθορισμένη θέση. Η συνάρτηση θα πρέπει να επιστρέφει το στιγμιότυπο ProgramWindow που της πέρασε, αφού εφαρμοστούν οι αλλαγές.

Το παράθυρο θα πρέπει να αποκτήσει πλάτος 400, ύψος 300 και να τοποθετηθεί στη θέση x = 100, y = 150.

const programWindow = new ProgramWindow();
changeWindow(programWindow);
programWindow.size.width;
// => 400

// Similar for the other fields.
Επεξεργασία μέσω GitHub Ο σύνδεσμος ανοίγει σε νέο παράθυρο ή καρτέλα
JavaScript Exercism

Έτοιμος να ξεκινήσεις την άσκηση Σύστημα Παραθύρων;

Γράψου στο Exercism για να μάθεις και να κατακτήσεις JavaScript με 37 έννοιες159 ασκήσεις και πραγματική καθοδήγηση από ανθρώπους, όλα δωρεάν.