Skip to content

13. Klasy

Image

W tym miejscu wprowadzamy klasy ECMAScript 6. Najpierw pokazujemy, że funkcje mogą być już wykorzystywane jako klasy.

13.1. skrypt [class-00]

Poniższy skrypt ilustruje nietypowe zastosowanie funkcji. Wykorzystujemy je tutaj jako obiekty.


'use strict';
// funkcja może być używana jako obiekt

// pusta skorupa
function f() {

}
//, któremu przypisuje się właściwości z zewnątrz
f.prop1 = "val1";
f.show = function () {
  console.log(this.prop1);
}
// użycie funkcji f
f.show();

// funkcja g działająca jak klasa
function g() {
  this.prop2 = "val2";
  this.show = function () {
    console.log(this.prop2);
  }
}
// instancjonowanie funkcji za pomocą [new]
new g().show();

Komentarze

  • wiersze 5–7: treść funkcji f nie definiuje żadnych właściwości;
  • wiersze 9–12: z zewnątrz nadaje się właściwości funkcji f;
  • wiersz 14: użycie funkcji (obiektu) f. Należy zauważyć, że nie pisze się [f()], lecz po prostu [f]. Jest to notacja obiektu;
  • wiersze 17–22: definiuje się funkcję [g] tak, jakby była to klasa z właściwościami i metodami;
  • wiersz 24: funkcja [g] jest instancjonowana przez [new g()];

Wyniki wykonania


[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\classes\class-00.js"
val1
val2

ES6 wprowadziła pojęcie klasy, dzięki czemu możemy teraz uniknąć korzystania z funkcji w celu uzyskania klas.

13.2. skrypt [class-01]

Skrypt [class-01] przedstawia klasę [Personne]:


// klasa
class Personne {

  // konstruktor
  constructor(nom, prénom, âge) {
    this.nom = nom;
    this.prénom = prénom;
    this.âge = âge;
  }

  // metody pobierające i ustawiające
  get nom() {
    return this._nom;
  }
  set nom(value) {
    this._nom = value;
  }

  get prénom() {
    return this._prénom;
  }
  set prénom(value) {
    this._prénom = value;
  }

  get âge() {
    return this._âge;
  }
  set âge(value) {
    this._âge = value;
  }

  // toString w JSON
  toString() {
    return JSON.stringify(this);
  }
}

// wywołanie klasy
function main() {
  const personne = new Personne("Poirot", "Hercule", 66);
  console.log("personne=", personne.toString(), typeof (personne), personne instanceof (Personne));
}

// wywołanie metody main
main();

Komentarze

  • wiersz 2: słowo kluczowe [class] oznacza klasę;
  • wiersze 5–9: słowo kluczowe [constructor] oznacza konstruktor klasy. Może występować co najwyżej jeden. Służy on do utworzenia i zainicjowania instancji klasy. Należy zauważyć, że nie ma deklaracji właściwości [nom, prénom, âge];
  • wiersze 11–36: właściwości klasy. Znajdują się tu elementy omówione już w rozdziale poświęconym obiektom 4 na stronie 44. Różni się jedynie składnia;
  • wiersz 41: utworzenie obiektu typu [Personne]. Od tego momentu obiekt [personne] jest używany jako obiekt literałowy. [typeof (personne)] ma wartość „object”, a wyrażenie [personne instanceof (Personne)] jest prawdziwe. Możliwe jest zatem ustalenie dokładnego typu instancji klasy;

Wyniki wykonania


[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\classes\class-01.js"
personne= {"_nom":"Poirot","_prénom":"Hercule","_âge":66} object true

13.3. skrypt [class-02]

Ten skrypt pokazuje możliwość dziedziczenia po klasie za pomocą słowa kluczowego [extends].

Najpierw wyodrębniamy klasę [Personne] do modułu [Personne.js]:


// klasa
class Personne {

  // konstruktor
  constructor(nom, prénom, âge) {
    this.nom = nom;
    this.prénom = prénom;
    this.âge = âge;
  }

  // metody pobierające i ustawiające
  get nom() {
    return this._nom;
  }
  set nom(value) {
    this._nom = value;
  }

  get prénom() {
    return this._prénom;
  }
  set prénom(value) {
    this._prénom = value;
  }

  get âge() {
    return this._âge;
  }
  set âge(value) {
    this._âge = value;
  }

  // toString w JSON
  toString() {
    return JSON.stringify(this);
  }
}
// eksport klasy
export default Personne;
  • wiersz 39: eksportujemy klasę [Personne], aby skrypty mogły ją importować;

Skrypt [class-02] tworzy klasę [Enseignant] wywodzącą się z klasy [Personne]:


// importy
import Personne from './Personne';

// klasa
class Enseignant extends Personne {

  // konstruktor
  constructor(nom, prénom, âge, discipline) {
    super(nom, prénom, âge);
    this.discipline = discipline;
  }

  // metody pobierające i ustawiające
  get discipline() {
    return this._discipline;
  }
  set discipline(value) {
    this._discipline = value;
  }

}

// wywołanie klasy
function main() {
  const enseignant = new Enseignant("Poirot", "Hercule", 66, "détective");
  console.log("enseignant=", enseignant.toString(), typeof (enseignant), enseignant instanceof Enseignant);
}

// wywołanie metody main
main();

Komentarze

  • wiersz 2: importujemy klasę [Personne] z modułu [Personne.js], który znajduje się w tym samym folderze co [class-02];
  • wiersz 5: klasa [Enseignant] rozszerza (dziedziczy po) klasie [Personne] za pomocą słowa kluczowego [extends]: dodaje do niej właściwość [_discipline] wraz z odpowiednimi metodami getter i setter;
  • wiersze 8–11: konstruktor klasy [Enseignant] otrzymuje cztery wartości w celu zainicjowania czterech właściwości klasy;
  • wiersz 9: słowo kluczowe [super] wywołuje konstruktor klasy nadrzędnej [Personne], który z kolei zainicjuje właściwości [_nom, _prénom, _âge];
  • wiersz 10: inicjowana jest właściwość [_discipline] należąca do klasy [Enseignant];
  • wiersze 14–19: metody getter i setter właściwości [_discipline];
  • wiersz 25: tworzy się obiekt typu [Enseignant];
  • wiersz 26: wywołano metodę [enseignant.toString()]. Klasa [Enseignant] nie posiada tej metody. W takiej sytuacji automatycznie wykorzystywana jest metoda klasy nadrzędnej. Metoda ta sprawia, że wyrażenie [JSON.stringify(this)], gdzie [this] będzie tutaj obiektem [Enseignant], a nie obiektem [Personne]. W programowaniu obiektowym nazywa się to polimorfizmem klas. To wielkie słowo jak na JavaScript, który nie jest językiem obiektowym. Niemniej jednak JavaScript spełnia tutaj swoje zadanie: prawidłowo wyświetla nauczyciela;

Wyniki wykonania są następujące:


[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\classes\class-02.js"
enseignant= {"_nom":"Poirot","_prénom":"Hercule","_âge":66,"_discipline":"détective"} object true
  • wiersz 2: JavaScript prawidłowo rozpoznaje, że zmienna [enseignant] jest typu [Enseignant];

13.4. skrypt [class-03]

Skrypt [class-03] pokazuje, że klasa potomna może redefiniować właściwości i metody swojej klasy nadrzędnej. W tym przypadku redefiniujemy metodę [toString] klasy nadrzędnej:


// importy
import Personne from './Personne';

// klasa
class Enseignant extends Personne {

  // konstruktor
  constructor(nom, prénom, âge, discipline) {
    super(nom, prénom, âge);
    this.discipline = discipline;
  }

  // metody pobierające i ustawiające
  get discipline() {
    return this._discipline;
  }
  set discipline(value) {
    this._discipline = value;
  }

  // przedefiniowanie toString
  toString() {
    return "[Enseignant]" + JSON.stringify(this);
  }
}

// wywołanie klasy
function main() {
  const enseignant = new Enseignant("Poirot", "Hercule", 66, "détective");
  console.log("enseignant=", enseignant.toString(), typeof (enseignant), enseignant instanceof Enseignant);
}

// wywołanie metody main
main();

Wyniki wykonania są następujące:


[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\classes\class-03.js"
enseignant= [Enseignant]{"_nom":"Poirot","_prénom":"Hercule","_âge":66,"_discipline":"détective"} object true

13.5. skrypt [class-04]

Skrypt [class-04] ponownie ilustruje działanie polimorfizmu: tam, gdzie funkcja oczekuje parametru formalnego typu [Personne], można przekazać typ pochodny, taki jak [Enseignant]. W rzeczywistości, dzięki derywacji, typ [Enseignant] posiada wszystkie atrybuty typu [Personne].

Najpierw wyodrębniamy typ [Enseignant] do modułu [Enseignant.js]:


// importy
import Personne from './Personne';

// klasa
class Enseignant extends Personne {

  // konstruktor
  constructor(nom, prénom, âge, discipline) {
    super(nom, prénom, âge);
    this.discipline = discipline;
  }

  // metody pobierające i ustawiające
  get discipline() {
    return this._discipline;
  }
  set discipline(value) {
    this._discipline = value;
  }

}

// eksport klasy
export default Enseignant;
  • wiersz 24: klasa [Enseignant] jest eksportowana, aby inne skrypty mogły ją zaimportować;

Skrypt [class-04] wygląda następująco:


// importy
import Enseignant from './Enseignant';
import Personne from './Personne';

// funkcja przyjmująca jedną osobę jako parametr
function show(personne) {
  // we wszystkich przypadkach
  console.log("paramètre=", personne.toString(), typeof (personne));
  // instancja klasy Osoba
  if (personne instanceof Personne) {
    console.log("personne=", personne.toString());
  }
  // instancja klasy Nauczyciel
  if (personne instanceof Enseignant) {
    console.log("enseignant=", personne.toString());
  }
}

// wywołanie funkcji show z nauczycielem
show(new Enseignant("Poirot", "Hercule", 66, "détective"));
show(new Personne("Marple", "Miss", 70));
  • wiersz 6: funkcja [show] oczekuje typu [Personne] lub pochodnego;
  • wiersz 8: wyświetlany jest ciąg parametryczny wraz z jego typem. Znajdziemy tam [object];
  • wiersze 10–16: można ustalić, czy jest to typ [Personne], czy typ [Enseignant]. Kod można zatem dostosować do rzeczywistego typu parametru;

Wyniki wykonania są następujące:


[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\classes\class-04.js"
paramètre= {"_nom":"Poirot","_prénom":"Hercule","_âge":66,"_discipline":"détective"} object
personne= {"_nom":"Poirot","_prénom":"Hercule","_âge":66,"_discipline":"détective"}
enseignant= {"_nom":"Poirot","_prénom":"Hercule","_âge":66,"_discipline":"détective"}
paramètre= {"_nom":"Marple","_prénom":"Miss","_âge":70} object
personne= {"_nom":"Marple","_prénom":"Miss","_âge":70}
  • wiersze 4 i 6: JavaScript poprawnie rozpoznaje typ instancji klasy;