ModuleNr.2 W tym wpisie zobaczymy jak moduły i klasy działają w JavaScript. Wiem, co sobie myślisz "przecież ja o tym wiem", ale czy tak rzeczywiście jest. Nie zaszkodzi zrobić szybką powtórkę i odświeżyć wiedzę. 

Jeśli zaczynasz swoją przygodę z JavaScript, to ten wpis jeszcze bardziej Ci się przyda. O ile wcześniej do testowania kodu JavaScript wystarczyła sama konsola przeglądarki. Tym razem będę potrzebował prymitywny projekt.  

Utworzyłem w Visual Studio Code 3 pliki index.html, main.js i module-first.js.

projekt visula studio.PNG

Strona HTML zawiera w sobie referencję do tych plików.

<<!DOCTYPE html>
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title>Szybki trening</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <script type="module" src="/module-first.js"   ></script>
        <script type="module" src="/main.js" ></script>
    </head>
    <body>

    </body>
</html>

Nie obejdzie się bez serwera. Otrzymasz błąd cross-domain, jeśli uruchomisz kod ze ścieżki fizycznej. Stworzyłem więc też plik package.json, aby taki serwer sobie zainstalować.

{
    "name": "SzybkiTrening",
    "version": "1.0.0",
    "description": "SzybkiTrening",
    "scripts": {
      "lite": "lite-server --port 10001", 
      "start": "npm run lite"
    },
    "author": "",
    "license": "ISC",
    "devDependencies": {
      "lite-server": "^1.3.1"
    }
 }

Wpisuje w terminal polecenie instalacji. 

npm install

Później uruchamiam sam serwer.

npm run lite

Teraz będę mógł testować kod pod adresem localhost: 10001. Co więcej, każda zmiana w kodzie automatycznie uruchomi mój serwer ponownie. Co ułatwi testowanie.

projekt visula studio_01.PNG

Ty nie musisz tego robić. Chciałem Ci pokazać, jednak co trzeba zrobić, aby te przykłady z modułami i klasami działały. Myślę, że jest to takie proste, że sam mógłbyś spróbować.

Podstawy modułów

W pliku głównym chce zaimportować zmienną "worstGameEver" z modułu "module-frist.js". Jak widzisz, w module-first.js mam polecenie "export" które deklaruje tą zmienną.

// Plik main.js
import {worstGameEver} from '/module-first.js';
console.log(worstGameEver);
// Plik module-first.js
export let worstGameEver = "Last of US 2";

Jak myślisz? Co napiszę ten kod w konsoli? Oczywiście, że wartość "Last of US 2". Jest najprostszy przykład komunikacji pomiędzy modułami. Spójrzmy na następny przykład.

Teraz importuje kilka zmiennych z modułu 

// Plik module-first.js
export let worstGameEver = "Last of US 2";
export let bestGameEver = "Silent Hill 2";
// Plik main.js
import {worstGameEver, bestGameEver} from '/module-first.js';
console.log(`Najgorsza gra ${worstGameEver}, Najlepsza gra ${bestGameEver}`);

Taki kod jest poprawny. Dodatkowo możesz zmieniać nazwy tych zmiennych, używając słowa kluczowego "as".

// Plik main.js
import {worstGameEver as bad, bestGameEver as good} from '/module-first.js';
console.log(`Najgorsza gra ${bad}, Najlepsza gra ${good}`);

Jeśli korzystasz z aliasu, to nie możesz już skorzystać z oryginalnej nazwy zmiennej. Taki kod więc...

Wyrzuci błąd:

Uncaught SyntaxError: The requested module '/module-first.js' does not provide an export named 'bestGameEver'

Jaka jest kolejność działań exportu i importu kodu JavaScript. Spójrz na ten przykład. Jak myślisz. Co wyświetli się w konsoli dokładnie.

// Plik main.js
console.log('zaczynamy w main.js');
import {worstGameEver} from '/module-first.js';
console.log('koniec w main.js');
// Plik module-first.js
export let worstGameEver = "Last of US 2";
console.log('jesteśmy w module-first.js');

Konsola pokaże: 

jesteśmy w module-first.js
zaczynamy w main.js
koniec w main.js

Jak widzisz, kod modułu wykonał się najpierw, mimo iż jego import był zadeklarowany niżej.

Co tu się stało? Stwierdzenie import zostało podane "hoisting" czyli zostało windowane w górę.

Przechodzimy do kolejnego przykładu. Tym razem nie mam nawiasów klamrowych przy imporcie i jak widzisz, w module używam "export default". 

// Plik main.js
import value from '/module-first.js';
console.log(value);
// Plik module-first.js
export let worstGameEver = "Last of US 2";
let game = "Settlers";
export default game;

Co zwróci mi konsola? W konsoli pojawi się wartość "Settlers". Możesz też jawnie powiedzieć, że chcesz mieć wartość domyślną z danego modułu.

// Plik main.js
import {default as game} from '/module-first.js';
console.log(game);

Czy można eksportować więcej niż jedyną zmienną? Zobaczmy. Na chłopski rozum można napisać taki kod.

// Plik main.js
import value from '/module-first.js';
console.log(value);
// Plik module-first.js
let worstGameEver = "Last of US 2";
let game = "Settlers";
export {worstGameEver,game}

Co w takim razie pojawi się w konsoli w takim razie. Jak widzisz w kodzie, poniżej nie określamy wartości domyślnej. Co pojawi się w konsoli?

undefined

Nie mam tutaj domyślnej wartości, więc dla JavaScriptu nie jest jasne, co ma on tutaj zrobić. Ustawia więc wartość na undefined.

Co, jednak gdybyś chciał, zaimportować wszystko to, co posiada dany moduł. W następnym przykładzie używam znaku *, który referuje się do wszystkich elementów, które są oznaczone w module do eksportu.

W module-frist.js jak widzisz, eksportuje dwie zmienne.

// Plik main.js
import * as values from '/module-first.js';
console.log(values);
// Plik module-first.js
let worstGameEver = "Last of US 2";
let game = "Settlers";
export {worstGameEver,game}

Co pojawi się w konsoli? Czy będzie tablica?

Module {Symbol(Symbol.toStringTag): "Module"}
game: "Settlers"
worstGameEver: "Last of US 2"

Gdzie tam. Będzie to obiekt modułu, który we właściwościach będzie zawierał moje wyeksportowane wcześniej zmienne.

Warto zaznaczyć, że gdy używasz "*" to stworzenie aliasu jest wymagane.

Eksport

Mamy kolejny przykład.

// Plik main.js
import {worstGameEver} from '/module-first.js';

worstGameEver = "E.T";
console.log(worstGameEver);
// Plik module-first.js
export let worstGameEver = "Last of US 2";

Jak myślisz? Jak ten kod zadziała. Dostaniemy błąd Runtime informujący nas, że zmienna "worstGameEver" jest tylko do odczytu. 

Czyli możemy uzyskać dostęp do zmiennej "worstGameEver", ale nie możemy jej zmienić.

Co jednak się stanie, jeżeli ta zmienna będzie przechowywać bardziej złożony obiekt. Teraz tylko zmienię właściwość tego obiektu.

// Plik main.js
import {worstGameEver} from '/module-first.js';

worstGameEver.name = "E.T";
console.log(worstGameEver.name);
// Plik module-first.js
export let worstGameEver = {
    name : 'Last of US 2'
}

W konsoli pojawi się wartość: E.T. O ile obiekt jest tylko do odczytu, to możesz oczywiście modyfikować jego właściwości.  Podobnie było ze stałymi, w poprzednim wpisie jak pamiętasz.

Jak ten obiekt jednak żyje pomiędzy tymi plikami JavaScript. Oto zaawansowany przykład. 

W module utwórzmy funkcję, która wyświetli właściwość naszego obiektu. Natomiast w głównym pliku JavaScript zmienimy wartość tej właściwości i potem uruchomimy tę funkcję. 

// Plik main.js
import {worstGameEver, showGame} from '/module-first.js';

worstGameEver.name = "E.T";
showGame();
console.log(worstGameEver.name);
// Plik module-first.js
export let worstGameEver = {
    name : 'Last of US 2'
};

export function showGame() {
    console.log(worstGameEver.name);
}

Co pojawi się w konsoli. Dwa razy wartość: E.T, E.T. Gdy eksportuje wartości z jednego modułu do drugiego, to ta wartość jest synchronizowana ze wszystkim modułami. 

Poza tym pomyśli, co by się działo, gdyby tak nie było. Spójrz na przykład na taki kodu. Kolejność działań powinna być oczywista dla programisty.

Jak myślisz, czy funkcja ulegnie zmianie, czy nie.

// Plik main.js
import {updateShowGame, showGame} from '/module-first.js';

showGame();
updateShowGame();
showGame();;
// Plik module-first.js
export function showGame() {
    console.log('orginał');
}
export function updateShowGame() {
    showGame = function() {console.log('coś nowego');};
}

Co pojawi się w konsoli?

Pojawi się oryginalna funkcja najpierw, apotem pojawi się nowa funkcja. To wszystko na temat modułów. Teraz przejdź my do klas.

Klasy w JavaScript

Czym są dokładnie klasy w JavaScript? Jak bardzo one się różnią od klas znanych z C# i Javy.

Ktoś ci powie, że to tylko nowa składnia języka i pod spodem jest coś, co dobrze znasz.

class Worker {

}
console.log(typeof Worker);

Jak myślisz, co pojawi się w konsoli. 

Okazuje się, że klasa tak naprawdę jest funkcją. Jakiego typu jest instancja klasy Worker?

W konsoli dowiesz się, że jest to obiekt. Teraz mamy podobny kod, ale korzystam z polecenia "instanceof"

class Worker {

}
let worker = new Worker();
console.log(worker instanceof Worker);

W konsoli pojawi się wartość: true, ponieważ obiekt ten jest przecież instancją klasy Worker.

Jak widzisz, jesteśmy w stanie określić, czyją instancją klasy jest dany obiekt. 

W klasach możesz mieć metody. Oczywiście rodzi się pewne pytanie. Skoro klasa jest tak naprawdę funkcją to, gdzie żyje zawarta w nim funkcja.

Sprawdźmy, czy funkcja "sayWork" to ta sama funkcja, która siedzi prototype. Co pokaże się w konsoli?

class Worker {
    sayWork() {
        console.log('work work')
    }
}
let worker = new Worker();
console.log(worker.sayWork === Worker.prototype.sayWork);

Dostaniemy wartość "true". Czyli dodanie metody do klasy jest równe dodaniu metody do obiektu prototype. To jest dobry przykład, który pokazuje, jak naprawdę jest zbudowana klasa i zawarte w niej funkcję.

Oto składnia kodu definicji klasy z konstruktorem.

class Worker {
    constructor() {
        console.log('buduje Worker');
    }
    sayWork() {
        console.log('work work')
    }
}
let worker = new Worker();

!_11.png

Z drugiej strony jak sobie przypominasz, gdy pracujemy z obiektami w JavScript, to każda nowa właściwość musi być oddzielona przecinkiem. 

Co się stanie, jeżeli dodasz przecinek do definicji klasy.

class Worker {
    constructor() {
        console.log('buduje Worker');
    },
    sayWork() {
        console.log('work work')
    }
}

W konsoli wyskoczy ci błąd składni. Gdy definiujesz klasę, przecinki nie są ci potrzebne. 

Jak więc zdefiniować właściwość/pole wewnątrz klasy? Jak myślisz, czy ten styl jest poprawny?

class Worker {
    let workerId = 1;
    constructor() {
        console.log('buduje Worker');
    }
    sayWork() {
        console.log('work work')
    }
}
let worker = new Worker();

!_12.png

Patrząc na inne języki programowania, to mógłbyś powiedzieć, że wygląda to dobrze. 

To JavaScript w taki wypadku zwróci Ci błąd. O tworzeniu właściwości i statycznych pól opowiem Ci później.

Czytając ten wpis i poprzedni zapewne już rozumiesz, o co chodzi z hoisting, czyli z windowaniem. Spójrz na ten przykład. Tworze nową instancję klasy Worker, ale definicja tej klasy będzie niżej w kodzie.

Jak myślisz, taki kod wykona się poprawnie?

let worker = new Worker();
class Worker {
    constructor() {
        console.log('buduje Worker');
    }
}

Dostaniesz błąd informujący Cię o tym, że nie możesz użyć klasy przed jej deklaracją. 

Klasy więc nie są windowane.

Klasy w JavaScript mogą być używane wewnątrz swojego wyrażenia. Oznacza to, że mogę zrobić coś takiego. W sumie klasa to funkcja więc nie powinno nas dziwić, że coś takiego jest możliwe.

let worker = class Worker {
    constructor() {
        console.log('buduje Worker');
    }
};
new worker();

!_13.png

Czy mogę stworzyć nową instancję klasy w taki sposób? Co pojawi się w konsoli?

Dostaniesz informację, że klas zbudowała się poprawnie. Możesz więc przypisywać definicję klas do zmiennych i potem tworzyć obiekty na podstawie tej definicji.

Spójrz na kod poniżej. Nie wygląda to trochę podobnie do poprzedniego kodu.

let Worker = function() {

    console.log('buduje Worker');
};
let worker  = {};
Worker.call(worker);

!_14.png

Ten kod wyświetli Ci w konsoli dokładnie to samo poprzedni kod.

Sprawdźmy jednak, czy rzeczywiście zachowania są dokładnie takie same. Sprawdźmy, czy ten kod zdziała. Chcemy wywołać konstruktor klasy dla nowego obiektu. 

class Worker {

    constructor() {
        console.log('Buduje Worker');
    }
}
let worker  = {};
Worker.call(worker);

!_15.png

W konsoli w takim wypadku otrzymasz błąd informujący cię o tym, że nie możesz wywołać konstruktora klasy wewnątrz jego konstruktora. 

Nie możesz wywołać funkcji konstruktora, aby zmienić jakiś obiekt.

Gdzie domyślnie żyją te wszystkie klasy? Najpierw zobaczmy, gdzie żyją zdefiniowane przez nas funkcję. Utworzę najpierw funkcję i sprawdzę, czy istnieje ona w globalnym obiekcie Window po deklaracji.

function Solider() {}
console.log(window.Solider === Solider);

Taki kod zwróci Ci prawdę. Jak będzie z klasą? W końcu klasa to nowa składnia funkcji czyż nie.

class Solider {}
console.log(window.Solider === Solider);

!_16.png

W tak wypadku otrzymasz fałsz. Gdy tworzysz klasę, to nie zaśmiecasz globalnej przestrzeni nazw. Klasy nie są umieszczone do obiektu Window lub innego globalnego obiektu, jeśli pracujesz z Nodem.

Dziedziczenie z klasami w JavaScript

W JavaScript mamy dwa słowa kluczowe, aby obsłużyć mechanizm dziedziczenia znany w wielu językach obiektowych. Kiedyś trzeba było się bawić właściwością prototype, aby osiągnąć taki efekt. Teraz tego nie musisz robić.

Jak to więc działa z klasami.  Mamy tutaj klasę żołnierza i klasę żołnierza oprogramowania, która po niej dziedziczy.

Żołnierz ma konstruktor, druga klasa nie ma konstruktora. Jak myślisz, co pojawi się w konsoli.

class Solider {
    constructor() {
        console.log('Buduje Solider');
    }
}
class SoftwareSolider extends Solider {

}
let s = new SoftwareSolider();

!_17.png

Oczywiście konstruktor rodzica zostanie wywołany i wyświetli się ten tekst w konsoli.

Oto podobny przykład tylko tym razem konstruktor rodzica przyjmuje parametr. Czy taki kod jest poprawny?

class Solider {
    constructor(name) {
        console.log('buduje ' + name);
    }
}
class SoftwareSolider extends Solider {

}
let s = new SoftwareSolider('Czesława');

!_18.png

Tak. Otrzymasz napis "buduje Czesława". Jak widzisz, mimo iż SoftwareSolider, czyli ŻółnierzOprogramowania nie ma swojego konstruktora, to taki kod się wykona.

Oto ponownie nasze klasy. Teraz jednak w klasie SoftwareSolider mam konstruktor i w nim wywołuje specjalną funkcję super(). Tak funkcja powinna wywołać konstruktor bazowy z poprzedniej klasy.

Co pojawi się konsoli.

class Solider {
    constructor() {
        console.log('buduje Solider');
    }
}
class SoftwareSolider extends Solider {
    constructor() {
        super();
        console.log('buduje SoftwareSolider');
    }
}
let s = new SoftwareSolider();

!_19.png

Dostaniesz informację o tym, że najpierw wykonał się konstruktor Solider, a potem konstruktor SoftwareSolider. 

Wywołując, więc funkcję super w konstruktorze silnik JavaScript uruchamia poprzedni konstruktor.

Czy mogę zablokować wywołanie poprzedniego konstruktora? Po prostu nie użyje specjalnej funkcji super. Co pojawi się w konsoli, gdy napiszę taki kod?

class Solider {
    constructor() {
        console.log('buduje Solider');
    }
}
class SoftwareSolider extends Solider {
    constructor() {
        //super();
        console.log('buduje SoftwareSolider');
    }
}
let s = new SoftwareSolider();

!_20.png

Dostaniesz błąd referencji. Czyli jesteś zmuszony wywołać poprzedni konstruktor, jeśli twoja klasa dziedzicząca będzie miała swój.

JavaScript daje Ci wybór, kiedy chcesz użyć poprzedniego konstruktora. 

Teraz mam podobny przykład tylko teraz nasza klasa żołnierza nie ma konstruktora. Czy wtedy nie muszę wykonywać operacji "super"? Zobaczmy, co pojawi się w konsoli.

class Solider {
    // constructor() {
    //     console.log('buduje Solider');
    // }
}
class SoftwareSolider extends Solider {
    constructor() {
        //super();
        console.log('buduje SoftwareSolider');
    }
}
let s = new SoftwareSolider();

!_21.png

Tutaj niespodzianka, wciąż otrzymasz błąd referencji. Mimo iż nasz rodzic nie ma konstruktora to i tak klasa dziedzicząca po nim jest zmuszona wykonać operację "super".

Poprawny kod więc jest taki:

class Solider {
    constructor() {
        console.log('buduje Solider');
    }
}
class SoftwareSolider extends Solider {
    constructor() {
        super();
        console.log('buduje SoftwareSolider');
    }
}
let s = new SoftwareSolider();

Jak jest z metodami/funkcjami wewnątrz klas. Jeżeli znasz inne języki programowania, to spodziewasz sie tego, że metoda w klasie rodzica powinna być dostępna dla wszystkich klas po niej dziedziczących.

Czy rzeczywiście tak jest? Co wyświetli się w konsoli?

class Solider {

    getAttackPoints() {
        return 15;
    }
}
class SoftwareSolider extends Solider {

}
let s = new SoftwareSolider();
console.log(s.getAttackPoints());

!_22.png

Dostaniesz wartość 15, czyli tak JavaScript wspiera wywołanie funkcji swojego rodzica.

Co się jednak stanie, gdy klasa rodzica i klas dziedziczącą po nim ma tę samą funkcję. Otrzymam teraz wartość 15 czy 30?

class Solider {

    getAttackPoints() {
        return 15;
    }
}
class SoftwareSolider extends Solider {
    getAttackPoints() {
        return 30;
    }
}
let s = new SoftwareSolider();
console.log(s.getAttackPoints());

Otrzymasz wartość 30. Dlaczego? Mimo iż JavaScript nie ma swojego specjalnego słowa na przeciążania metod i funkcji to takie zachowanie jest całkowicie poprawne. 

W tym przykładzie nadpisaliśmy funkcję rodzica - tworząc funkcję o tej samej nazwie.

Czy możesz wywołać funkcję swojego rodzica w klasie po niej dziedziczącej? Zobaczmy, jak słowo kluczowe super w tym przypadku się sprawdzi. 

class Solider {

    getAttackPoints() {
        return 15;
    }
}
class SoftwareSolider extends Solider {
    getAttackPoints() {
        return super.getAttackPoints() + 16;
    }
}
let s = new SoftwareSolider();
console.log(s.getAttackPoints());

!_23.png

Dostaniesz wartość 31. Czyli tak używając "super" możesz odwoływać się funkcji rodzica. Oznacza to też, że w JavaScript możesz spokojnie zrobić wzorzec projektowy dekorator.

Używaliśmy teraz klasy i dziedziczenia. Teraz jakbyś napisał takie zachowanie bez nich? Jak by to działało na zmiennych z przypisanymi do siebie obiektami. 

Czy możemy użyć słowa kluczowego super(), gdy skorzystamy z mechanizmu setPrototypeOf()? Ta funkcja informuje JavaScript, że jeden obiekt jest powiązany z drugim w relacji dziedziczenia, czy tam jest prototypem drugiego.

Co pojawi się w konsoli?

let Solider ={
    getAttackPoints() {
        return 15;
    }
};

let SoftwareSolider = {
    getAttackPoints() {
        return super.getAttackPoints() + 16;
    }
};

Object.setPrototypeOf(SoftwareSolider,Solider);
console.log(SoftwareSolider.getAttackPoints());

!_24.png

W konsoli zobaczysz 31. Czyli korzystanie ze słowa kluczowego super jest w pełni poprawne, jeśli mówimy o obiektach. Musisz tylko odpowiednio ustawić prototyp.

Właściwości i instancje klas

Jak deklarujemy właściwości w klasach. Używam słowa kluczowego this. Oto przykład stworzenia właściwości name w klasie bazowej. Czy właściwość także będzie w klasie po niej dziedziczącej?

class Solider {
    constructor() {
        this.name = "Czesław";
    }
}
class SoftwareSolider extends Solider {
    constructor() {
        super();
    }
}

let s = new SoftwareSolider();
console.log(s.name);

!_25.png

Oczywiście, że tak. W końcu jak pamiętasz z poprzednich przykładów, to konstruktor bazowy też się uruchamia w klasie po niej dziedziczącej. 

Oto zbliżony przykład, ale teraz do deklaracji używam słowa "let". Jak myślisz, co pojawi się konsoli?

class Solider {
    constructor() {
        let name = "Czesław";
    }
}
class SoftwareSolider extends Solider {
    constructor() {
        super();
    }
}

let s = new SoftwareSolider();
console.log(s.name);

!_26.png

Otrzymasz wartość "undefined". W końcu zmienna zadeklarowana po wykonaniu konstruktora przestaje istnieć i nie przylepi się do instancji klasy.

Nie problemu z uzyskaniem właściwości w wyniku dziedziczenia. Sprawdźmy jednak dla pewności.

class Solider {
    constructor() {
        this.name = "Czesław";
    }
}
class SoftwareSolider extends Solider {
    constructor() {
        super();
        this.name = this.name +  " Pfu";
    }
}

let s = new SoftwareSolider();
console.log(s.name);

W konsoli otrzymasz wartość "Czesław Pfu".

Tylko w taki sposób możesz tworzyć właściwości. Rodzi się pytanie, jak te właściwości zmieniać, gdy już twój konstruktor się wykonał. To proste musisz utworzyć dla każdej takiej właściwości funkcję GET; SET.

Ten styl programowania jest znany z Javy i C#.

class Solider {
    constructor() {
        this.name = "Czesław";
    }

    getName() {
        return this.name; 
    }

    setName(val) {
        this.name = val;
    }
}

O ile moim zdaniem deklarowanie wszystkich właściwości w konstruktorze jest dobrą praktyką. Taki kod też jest poprawny. Jest to też nowość języku JavaScript, więc możesz mieć niespodziankę, jeśli taki kod nie zadziała.

class User {
  name = "Anonymous";

  sayHi() {
    console.log(`Hello, ${this.name}!`);
  }
}
new User()

!_27.png

Na chwilę obecną to, co widzisz, jest wczesnej fazie eksperymentalnej. 

javascritp2.PNG

Czy klasa to rzeczywiście tylko funkcja

Gdybyś chciał stworzyć klasę, nie używając słowa class, to mógłbyś to zrobić to tak.

// przepisanie klasy User na czystą funkcję

// 1. stworzenie konstuktora funkcji
function Worker(name) {
  this.name = name;
}

// 2. Dodajemy metode do prototypu
Worker.prototype.sayHi = function() {
  console.log(this.name);
};

// Użycie
let worker = new Worker("John");
worker.sayHi();

!_28.png

Mimo to i tak tak są różnice. Po pierwsze funkcja oznaczona słowem class ma specjalną właściwość [[FunctionKind]]:"classConstructor". Oznacza to, że ten kod powyżej nie jest dokładnym porównaniem. 

Ta właściwość to informacja dla kompilatora "czy używasz słowa kluczowego "new" przy tworzeniu instancji klasy".

class Worker{
  constructor() {}
}

alert(typeof Worker); // function
Worker(); 
// Error: Class constructor User cannot be invoked without 'new'

Poza tym klasy zawsze używają trybu "strick mode". Metody klasy domyślnie mają też ustawioną flagę enumerable na false. Czyli jeśli użyjesz pętli for..in na obiekcie klasy, to nie dostaniesz jej funkcji/metod.

Statyczne pola

Statyczne pola deklarujesz, używając słowa kluczowego "static". Jak w innych językach programowania, aby uzyskać dostęp do pola statycznego, nie musisz tworzyć instancji tej klasy.

class Solider {
    static getDefaultAttack() {
        return 1;
    }
}
console.log(Solider.getDefaultAttack());

!_29.png

Jak myślisz, co się stanie, gdy będzie chciał odwołać się do statycznego pola, gdy mam instancję klasy.

class Solider {
    static getDefaultAttack() {
        return 1;
    }
}
let s = new Solider();
console.log(s.getDefaultAttack());

!_30.png

Otrzymasz błąd : Error : Object doesn't support property or method getDefaultAttack()

Gdy korzystasz z instancji klasy, to JavaScript oczekuje od Ciebie zapytań o właściwości i funkcję, a nie o pola statyczne.

Czy możesz zadeklarować statyczne pola?

class Solider {
    static let attack = 0;
}

!_31.png

Tak kod zwróci Ci błąd składni. Całkiem jednak nie dawno pojawiło się wsparcie dla pól, ale nie oczekuj, że ten kod zdziała dla każdej przeglądarki.

class Solider {
    static attack = 0;
}

!_32.png

Czy możesz zadeklarować statyczne pola także w taki sposób?

class Solider {
 
}
Solider.attack = 0;
console.log(Solider.attack);

!_33.png

Tak. Warto też pamiętać, że to rozwiązanie będzie działało wszędzie. 

new.target

ES6 stworzyła nowa właściwość, która nazywa się new.target. Wiemy, że new jest słowem kluczowym do tworzenia klas. 

To słowo kluczowe ma także swoją właściwość. Po co on jest?

Po pierwsze new.target może być użyty tylko w konstruktorze. Sprawdźmy, czym on jest.

class Solider {
 
    constructor() {
        console.log(typeof new.target);
    }
}
var s = new Solider();

Z konsoli dowiadujemy się, że jest on funkcją, ale jaką funkcją. Zobaczmy tę całą funkcję w konsoli.

class Solider {
 
    constructor() {
        console.log(new.target);
    }
}
var s = new Solider();

O to wynik konsoli:

class Solider {
 
    constructor() {
        console.log(new.target);
    }
}

Czyli właściwość new.target wskazuje bezpośrednio na konstruktor klasy. Wydaje się to mało użyteczne. Zobaczmy, jednak jak on działa, gdy mamy dziedziczenie.

class Solider {

    constructor() {
        console.log(new.target);
    }
}

class SoftwareSolider extends Solider {

    constructor() {
        super();
    }
}

var s = new SoftwareSolider();

W tym kodzie dostaniesz 

constructor() {
        super();
}

new.target zwróci Ci więc konstruktor klasy, który został wezwany. 

Używając "new.target" możesz zobaczyć, jak wygląda domyślny konstruktor dla klas.

class Solider {

    constructor() {
        console.log(new.target);
    }
}

class SoftwareSolider extends Solider {
}

var s = new SoftwareSolider();

Jakie jest jednak pożyteczne użycie tego new.target. Otóż może na przykład użyć funkcji statycznej swojego dziecka w klasie rodzica. 

class Solider {

    constructor() {
        console.log(new.target.getDefaultAttack());
    }
}

class SoftwareSolider extends Solider {
    static getDefaultAttack() {return 1;}
}
var s = new SoftwareSolider();

Ten kod zwróci jeden.

Podsumowanie

To było na tyle, jeśli chodzi o klasy i moduły. Ten cykl jednak jeszcze się nie kończy.