Menu

Klasy w JavaScript: konstruktory, metody i słowo kluczowe new

Jak naprawdę działają klasy w JavaScript: konstruktory, metody, pola instancji, gettery i settery oraz model myślowy stojący za słowem kluczowym class.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Klasa to szablon obiektów

Klasa opisuje kształt i zachowanie pewnego rodzaju obiektów. Szablon definiujesz raz, a potem tworzysz tyle instancji, ile potrzebujesz. Każda instancja ma własne dane, ale wszystkie współdzielą te same metody.

Podstawowa postać:

class User { ... } definiuje szablon. new User(...) tworzy instancję. Każda instancja dostaje własne name i email, ale obie współdzielą jedną metodę greet zdefiniowaną w klasie.

Dwie instancje, dwa zestawy danych, jeden zestaw metod. Na tym polega cała idea.

constructor przygotowuje każdą instancję

constructor to specjalna metoda, która wykonuje się dokładnie raz dla każdej instancji, w chwili gdy new ją tworzy. Jej zadaniem jest zainicjowanie świeżego obiektu, zwykle przez skopiowanie argumentów do this:

this wewnątrz konstruktora wskazuje na zupełnie nową, właśnie budowaną instancję. Przypisanie this.x = x umieszcza x na tym konkretnym obiekcie. Każde wywołanie new Point(...) dostaje własne this, a więc własne x i y.

Jeśli nie napiszesz konstruktora, JavaScript da ci pusty za darmo. Musisz go pisać tylko wtedy, gdy faktycznie masz coś do przygotowania.

new wprawia cały mechanizm w ruch

Wywołanie klasy bez new rzuca błąd:

const p = Point(3, 4);
// TypeError: Class constructor Point cannot be invoked without 'new'

To celowe. new robi po kolei cztery rzeczy:

  1. Tworzy nowy pusty obiekt.
  2. Łączy go z prototypem klasy (gdzie mieszkają metody).
  3. Uruchamia constructor z this powiązanym z nowym obiektem.
  4. Zwraca nowy obiekt.

Bez new nic z tego się nie dzieje. Klasa wymusza tę regułę, więc nie da się o niej przypadkiem zapomnieć. To realna poprawa w porównaniu z czasami sprzed class, gdy zapomniane new po cichu psuło obiekt globalny.

Metody są współdzielone, pola należą do instancji

Metody zdefiniowane w ciele klasy mieszkają w prototypie: jedna kopia współdzielona przez wszystkie instancje. Pola instancji (wszystko, co przypisujesz do this) są osobne dla każdej instancji, za każdym razem to oddzielna kopia.

Obie instancje mają własne count, więc zwiększenie jednego nie wpływa na drugie. Ale a.increment i b.increment to dosłownie ta sama funkcja: zapisana raz w prototypie i wyszukiwana za każdym razem, gdy wywołujesz ją na instancji. Dlatego klasy tanio się skalują: tysiąc instancji nie tworzy tysiąca kopii każdej metody.

Pola klasy

Pola instancji możesz zadeklarować na początku ciała klasy, poza konstruktorem:

Deklaracje pól wykonują się przed ciałem konstruktora, tak jakby były zapisane na początku constructor. Przydają się, gdy wartość początkowa nie zależy od argumentów konstruktora: to czytelniejsze niż zaśmiecanie konstruktora przez this.count = 0; this.step = 1;.

Gdy wartości zależą od argumentów, zostaw przypisanie w konstruktorze, gdzie argumenty są w zasięgu.

Gettery i settery

Getter albo setter wygląda jak metoda, ale zachowuje się jak dostęp do właściwości. Odczytujesz albo przypisujesz wartość bez nawiasów, a pod spodem wykonuje się metoda:

Zwróć uwagę na miejsca wywołania: t.fahrenheit (bez nawiasów) odczytuje getter, a t.fahrenheit = 100 uruchamia setter. Z zewnątrz fahrenheit wygląda jak zwykła właściwość, ale jest obliczana na bieżąco z celsius.

Gettery świetnie sprawdzają się przy wartościach pochodnych. Settery świetnie nadają się do walidacji albo normalizacji wartości przy przypisaniu. Nie nadużywaj ich: jeśli getter jest kosztowny, czytelnik kodu zdziwi się, że "odczyt właściwości" wykonuje prawdziwą pracę.

Skrócony zapis metod, nazwy obliczane i this

Definicje metod w klasie używają skróconej składni: bez słowa kluczowego function i bez : między nazwą a ciałem:

Zwracanie this z add umożliwia łańcuchowe wywoływanie metod: każde wywołanie oddaje tę samą instancję, więc na niej może wykonać się kolejna metoda.

Jedna pułapka: metody nie są automatycznie powiązane ze swoją instancją. Jeśli wyciągniesz metodę i wywołasz ją osobno, this przepada:

Wywołanie g.hello() działa, bo . dostarcza this. Samodzielne wywołanie fn() już nie. Jeśli potrzebujesz odłączonej, wcześniej powiązanej metody (częsty przypadek w obsłudze zdarzeń), powiąż ją w konstruktorze albo użyj pola klasy z funkcją strzałkową: hello = () => \Hi, ${this.name}`;`.

Mały działający przykład

Składamy wszystkie elementy razem: pola, konstruktor, metody i getter:

Czytelne na pierwszy rzut oka: klasa opisuje dokładnie, czym jest BankAccount i co potrafi.

Klasy to tak naprawdę funkcje

Jedna rzecz, którą warto przyswoić: class to głównie lukier składniowy. Pod spodem klasa jest funkcją, a dokładnie funkcją konstruktora, a jej metody mieszkają w ClassName.prototype:

typeof User to "function". greet mieszka w User.prototype. Instancje znajdują greet przez łańcuch prototypów, czyli ten sam mechanizm, który jest w języku od pierwszego dnia. Klasy dają ci po prostu czystą składnię do jego ustawienia.

Ten model myślowy opłaca się później: dziedziczenie, instanceof i debugowanie łańcuchów prototypów stają się bardziej zrozumiałe, gdy pamiętasz, że klasa to funkcja z metodami zawieszonymi na jej prototypie.

Dalej: dziedziczenie

Na razie każda klasa stoi osobno. Większość prawdziwych projektów buduje hierarchie klas: Dog, który jest rodzajem Animal, albo AdminUser, który jest rodzajem User. Zajmują się tym słowa kluczowe extends i super, a o nich jest następna strona.

Najczęściej zadawane pytania

Jak utworzyć klasę w JavaScript?

Użyj słowa kluczowego class, po nim nazwy, a potem bloku z metodą constructor i innymi metodami. Instancję tworzysz przez new ClassName(...). Przykład: class User { constructor(name) { this.name = name; } }, a potem new User('Ada').

Co robi konstruktor w klasie JavaScript?

constructor wykonuje się raz, gdy wywołujesz new ClassName(...). Jego zadaniem jest przygotowanie nowej instancji, zwykle przez przypisanie argumentów do this jako właściwości instancji. Jeśli go nie napiszesz, JavaScript automatycznie da ci pusty konstruktor domyślny.

Czym różni się klasa od funkcji w JavaScript?

Pod spodem klasa jest funkcją, a dokładnie funkcją konstruktora z metodami podpiętymi do prototypu. Słowo kluczowe class to głównie lukier składniowy, ale wymusza wywołanie z new, czysto obsługuje extends i sprawia, że metody są niewyliczalne. W nowym kodzie wybieraj class zamiast ręcznie pisanych funkcji konstruktorów.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ