Jedna klasa zbudowana na drugiej
Dziedziczenie pozwala klasie zacząć od planu innej klasy i go rozszerzyć. Każde pole i metodę rodzica dostajesz za darmo, a do tego możesz dodać lub zmienić, co tylko potrzebujesz:
Dog extends Animal znaczy: „Dog to Animal, plus coś jeszcze”. rex nie ma własnej metody speak, ale wyszukiwanie przechodzi dalej do Animal, która ją ma. To przechodzenie dalej jest całym mechanizmem: to po prostu łańcuch prototypów z ładniejszą składnią.
super w konstruktorze
Podklasa z własnym konstruktorem ma jedną twardą zasadę: wywołaj super(...), zanim dotkniesz this. super uruchamia konstruktor rodzica, który faktycznie tworzy i inicjalizuje obiekt:
Pomiń linię super(name), a dostaniesz ReferenceError w chwili, gdy spróbujesz odczytać lub zapisać this. Silnik po prostu nie da ci this, dopóki rodzic nie zrobi swojego.
Jeśli podklasa nie deklaruje własnego konstruktora, JavaScript generuje taki, który przekazuje wszystkie argumenty do super. Własny konstruktor piszesz więc tylko wtedy, gdy dodajesz pola albo robisz dodatkową konfigurację.
Nadpisywanie metod
Podklasa może na nowo zdefiniować każdą odziedziczoną metodę. Wygrywa ta, która jest najbliżej w łańcuchu:
Żadnej magii: gdy wywołujesz speak() na obiekcie Dog, silnik szuka speak na instancji, potem na Dog.prototype, znajduje ją i się zatrzymuje. Do Animal.prototype w ogóle nie dochodzi.
Rozszerzanie zamiast zastępowania: super.method()
Czasem nie chcesz zastąpić metody rodzica, tylko coś do niej dodać. super.method(...) wywołuje wersję rodzica z wnętrza nadpisanej metody:
Tu dziedziczenie naprawdę się opłaca: podklasa ponownie wykorzystuje logikę rodzica zamiast ją kopiować. Jeśli Animal.describe później się zmieni, Dog.describe automatycznie przejmie zmianę.
super działa w każdej metodzie, nie tylko w konstruktorze. Zawsze odnosi się do wersji z klasy rodzica tego, co wywołujesz.
instanceof i łańcuch prototypów
instanceof sprawdza, czy łańcuch prototypów obiektu zawiera daną klasę. Każda instancja podklasy jest też instancją jej rodziców:
Wszystkie cztery dają true. Łańcuch wygląda tak: Puppy -> Dog -> Animal -> Object, a instanceof po nim przechodzi. Przydaje się do sprawdzania typów, choć w praktyce używa się go rzadziej, niż można by pomyśleć: większość kodu po prostu wywołuje metody, a resztą zajmuje się polimorfizm.
Trochę większy przykład
Częsty wzorzec: klasa bazowa ze wspólną logiką i kilka podklas, które ją specjalizują.
Zauważ, że describe mieszka w Shape i nigdy nie trzeba jej przepisywać: po prostu wywołuje this.area(), które w czasie działania programu trafia do właściwej podklasy. To polimorfizm: to samo wywołanie, różne zachowanie zależnie od faktycznego obiektu.
Dziedziczenie kontra kompozycja
Dziedziczenie kusi, bo daje poczucie produktywności: jedna linia i masz stos metod. Gorzej, gdy hierarchie rosną, bo wtedy źle się starzeje.
Praktyczna zasada: używaj extends, gdy relacja to wyraźnie „X jest Y”, a podklasa naprawdę współdzieli większość zachowania rodzica. Jeśli sięgasz po dziedziczenie tylko po to, żeby współdzielić jedną czy dwie metody pomocnicze, wybierz kompozycję: daj klasie pole przechowujące obiekt pomocniczy:
Głębokie drzewa dziedziczenia (Animal -> Mammal -> Dog -> WorkingDog -> PoliceDog) wyglądają schludnie na diagramach, ale w kodzie robią się bolesne: zmiana blisko korzenia rozchodzi się nieprzewidywalnie po wszystkich potomkach. Większość zdrowych projektów ma jeden lub dwa poziomy, a w pozostałych przypadkach opiera się na kompozycji.
Dalej: składowe statyczne
Wszystko w tym artykule żyje na instancjach: to metody, które wywołujesz przez new Thing().something(). Czasem jednak chcesz metod lub danych, które należą do samej klasy, a nie do konkretnej instancji. Do tego służy static i o tym jest następna strona.
Najczęściej zadawane pytania
Jak działa dziedziczenie w JavaScript?
Jedna klasa może dziedziczyć po innej dzięki extends. Podklasa dostaje każdą metodę i pole rodzica, a do tego może dodawać własne lub nadpisywać istniejące. Pod spodem JavaScript łączy prototyp podklasy z prototypem rodzica, więc wyszukiwanie metod automatycznie przechodzi dalej.
Co robi super w JavaScript?
super(...) wywołuje konstruktor klasy rodzica i musisz go wywołać, zanim użyjesz this w konstruktorze podklasy. super.method(...) wywołuje wersję metody z rodzica. W ten sposób rozszerzasz zachowanie zamiast całkowicie je zastępować.
Dziedziczenie czy kompozycja w JavaScript?
Używaj dziedziczenia, gdy istnieje prawdziwa relacja „jest” i podklasa współdzieli większość zachowania rodzica. Sięgnij po kompozycję, czyli obiekty przechowujące inne obiekty, gdy chcesz tylko współdzielić funkcjonalność. Głębokie hierarchie klas źle się starzeją, a większość prawdziwych projektów ma jeden lub dwa poziomy.