13. Klasy

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;