Trzecia część wpisu dotycząca obiektów JavaScript. Wiemy już jak najłatwiej tworzyć obiekty oraz jak działa dziedziczenie. Przyszła teraz kolej na najnowszy sposób pracy z obiektami – klasy JavaScript.

Czym są klasy JavaScript?

Klasy zostały wprowadzone wraz z najnowszą wersją JavaScript – ES6. Tak naprawdę zasada działania klas jest identyczna jak w przypadku tworzenia klas za pomocą konstruktora. Umożliwiają one jedynie łatwiejszy (czytelniejszy) zapis dla definiowania property oraz metod, które mają zostać dziedziczone przez kolejno tworzone instancje klasy. Bardzo przydatną rzeczą w klasach jest możliwość tworzenia klas, które rozszerzają istniejące już klasy o dodatkowe property lub metody.

Składnia klasy w JavaScript jest niezwykle prosta:

class Person {
  constructor(name, email) {
    // property są tworzone tutaj - wewnątrz metody 'constructor'
    // są one bezpośrednio dołączone do instancji klasy
    this.name = name;
    this.email = email;
  }
  
  // metody są tworzone tutaj - bezpośrednio w ciele klasy
  // są one dostępne jako 'prototype'
  login() {
    console.log(this.name, "własnie się zalogował");
  }

  logout() {
    console.log(this.name, "własnie się wylogował");
  }
}

const Mario = new Person("Mario", "mario@example.com");
const Luigi = new Person("Luigi", "luigi@example.com");

Co do znaczenia słowa kluczowego this powstanie osobny artykuł, ponieważ jest to dość szczegółowy temat, na chwile obecną wystarczy wiedzieć, iż słowo this wskazuje na konkretny obiekt, który został stworzony poprzez klasę. Klasa Person pozwoli nam stworzyć wiele obiektów które będą miały property name, dlatego musimy używać this, aby wskazać z którego z tych obiektów chcemy odczytać wartość tej property. Wstawiając this, wiemy, że chcemy tą wartość właśnie z obiektu na którym wykonujemy jakieś operacje.

Wiedząc już jak działa dziedziczenie w przypadku function constructor, wiemy co otrzymamy w konsoli gdy spróbujemy logować obiekt Mario:

logi z konsoli

Konstruktor klasy (metoda constructor) wywoływana jest zawsze jako pierwsza podczas tworzenia nowej instancji klasy. Wewnątrz konstruktora umieszczamy property, które chcemy, aby było bezpośrednio dołączone do obiektu. Możemy tam również umieści metody, ale nie jest to zalecane działanie.

Metody dużo lepiej jest umieszczać w ciele klasy, wtedy będą one dostępne poprzez prototype. Identycznie jak w przypadku function constructor.

Wywołanie teraz metody Mario.login() wyświetli nam w konsoli tekst Mario własnie się zalogował!, natomiast Luigi.login() oczywiście Luigi własnie się zalogował!

Dopowiem jeszcze dwa słowa na temat słowa new, którego używamy podczas tworzenia nowej instancji klasy. Za kulisami, wywołanie słowa new wykonuje nam następujące czynności:

  • tworzy nowy pusty obiekt {}
  • ustawia wartość this na ten dopiero co stworzony pusty obiekt
  • wywołuję metodę constructor

Przejdźmy teraz do funkcjonalności, której nieco brakowało w przypadku function constructor, czyli rozszerzania funkcjonalności (można było próbować to robić za pomocą Object.assign(), ale nie było to zbyt intuicyjne). Jak wiele osób może się teraz domyślać – wracamy do naszej przeglądarkowej gry oraz zarządzanie graczami. W drugiej części kursu pokazałem jak tworzyć nowych użytkownik poprzez konstruktory. Przepiszmy więc szybko poprzednią logikę wykorzystując do tego klasy:

class Player {
  constructor(nick, email) {
    this.nick = nick;
    this.email = email;
  };

  shoot() { console.log("SHOOT!!!") }
  login() { console.log("Jestem zalogowany!") }
  logout() { console.log("Jestem wylogowany!") }
  moveLeft() { console.log("Idę w lewo!") } 
  moveRight() { console.log("Idę w prawo!") }
} 

const Player1 = new Player("Dragon", "janek@example.com");
const Player2 = new Player("Fenix", "john@example.com");
const Player3 = new Player("Kmaikadze", "tom@example.com");

Player1.shoot(); // "SHOOT!!!"

Wróćmy do problemu, który pojawił nam się w przypadku pojawienia się gracza o rozszerzonych możliwościach względem gracza w wersji podstawowej. Stworzenie teraz takiego obiektu jest bardzo łatwym zadaniem – wykorzystamy do tego słowo kluczowego extends:

class Leader extends Player {
  constructor(nick, email, team) {
    super(nick, email);     // pozwala na użycie property z klasy 'Player'
    this.team = team;       // nowa property tylko dla klasy 'Leader'
  };
  jump() { console.log("Mogę skakać!") }
  invite(nick) { console.log(nick, ", zapraszam Cię do mojego zespołu")}
}

const Player99 = new Leader("Snake", "frank@example.com", "team-red");

Player99.moveLeft(); // Idę w lewo!
Player99.jump(); // Mogę skakać! 
console.log(Player99.team) // team-red
Player99.invite("Dragon"); // Dragon , zapraszam Cię do mojego zespołu 

Player3.jump(); // ERROR!

Jak widać z powyższego przykładu, rozszerzanie klas o dodatkowe funkcjonalności nie jest niczym skomplikowanym. W przypadku gdy chcemy jedynie dodawać nowe metody, nie musimy ponownie deklarować konstruktora klasy. Wywołany zostanie konstruktor z rozszerzanej klasy – w naszym przypadku z klasy Player. Gdy jednak chcemy dodać również nowe property poprzez konstruktor, musimy użyć słowa kluczowego super, które daje nam dostęp do property z konstruktora klasy rozszerzanej.

Podsumowanie

Jesteśmy na końcu trzeciej części artykułu przeprowadzającego nas przez świat obiektów oraz dziedziczenia w JavaScript. Znamy już niemal wszystkie techniki, które pozwolą nam na świadomą pracę na obiektach. Nie zostaje więc nam nic innego niż samemu zacząć pisać dobry kod obiektowy.

Kamil Józwik

Front end developer👨‍💻 Autor małego narzędzia dla programistów - frontbook.dev oraz autor kolejnych postów na tym blogu. Ulubiony stack to React wraz z TypeScript oraz wszystko co "nowe" w tym pięknym dynamicznym front end-owym świecie 🙂

Dodaj komentarz