dziedziczenie klas jest sposobem, aby jedna klasa rozszerzyła inną klasę.
dzięki czemu możemy tworzyć nowe funkcjonalności na bazie istniejących.
słowo kluczowe „extends”
Załóżmy, że mamy klasę Animal
:
Oto jak możemy reprezentować animal
obiekt I Animal
Klasa graficznie:
…i chcielibyśmy aby utworzyć inny class Rabbit
.,
ponieważ króliki są zwierzętami, klasa powinna być oparta naAnimal
, mieć dostęp do metod zwierzęcych, aby króliki mogły robić to, co mogą robić „ogólne” zwierzęta.
składnia rozszerzająca inną klasę to: class Child extends Parent
.
utwórzmy class Rabbit
który dziedziczy z Animal
:
obiekt Rabbit
klasa ma dostęp zarówno do metody, takie jak rabbit.hide()
, a także do Animal
metod, takich jak rabbit.run()
.,
wewnętrznie,extends
słowo kluczowe działa przy użyciu starej, dobrej mechaniki prototypów. Ustawia Rabbit.prototype.]
na Animal.prototype
. Tak więc, jeśli metoda nie zostanie znaleziona w Rabbit.prototype
, JavaScript pobiera ją z Animal.prototype
.,
na przykład, aby znaleźć metodę rabbit.run
, silnik sprawdza (od dołu do góry na obrazku):
jak możemy przypomnieć z rozdziału natywne prototypy, sam JavaScript używa dziedziczenia prototypowego dla wbudowanych obiektów. Np. Date.prototype.]
to Object.prototype
. Dlatego daty mają dostęp do ogólnych metod obiektowych.,
extends
extends
.
na przykład funkcja generująca klasę nadrzędną:
function f(phrase) { return class { sayHi() { alert(phrase); } };}class User extends f("Hello") {}new User().sayHi(); // Hello
tutajclass User
dziedziczy z wynikuf("Hello")
.,
może to być przydatne dla zaawansowanych wzorców programowania, gdy używamy funkcji do generowania klas w zależności od wielu warunków i możemy je dziedziczyć.
nadpisywanie metody
teraz przejdźmy do przodu i nadpisywanie metody. Domyślnie wszystkie metody, które nie są określone w class Rabbit
są pobierane bezpośrednio” tak jak jest”z class Animal
.,
ale jeśli określimy naszą własną metodę w Rabbit
, na przykład stop()
to będzie ona używana zamiast:
class Rabbit extends Animal { stop() { // ...now this will be used for rabbit.stop() // instead of stop() from class Animal }}
zwykle nie chcemy całkowicie zastępować metody nadrzędnej, ale raczej budować na niej, aby dostosować lub rozszerzyć jej funkcjonalność. Robimy coś w naszej metodzie, ale wywołujemy metodę nadrzędną przed/po niej lub w procesie.
klasy dostarczają do tego słowa kluczowego"super"
.
-
super.method(...)
wywołanie metody nadrzędnej., -
super(...)
wywołanie konstruktora nadrzędnego (tylko wewnątrz naszego konstruktora).
na przykład, niech nasz królik autohide po zatrzymaniu:
teraz ma stop
metoda, która wywołuje rodzica super.stop()
w procesie.
nadpisywanie konstruktora
z konstruktorami robi się trochę trudniej.
do tej pory nie miał własnego constructor
.,
zgodnie ze specyfikacją, jeśli Klasa rozszerza inną klasę i nie ma constructor
, to generowane jest następujące „empty” constructor
:
class Rabbit extends Animal { // generated for extending classes without own constructors constructor(...args) { super(...args); }}
jak widzimy, w zasadzie wywołuje rodzica constructor
przekazując mu wszystkie argumenty. Dzieje się tak, jeśli nie napiszemy własnego konstruktora.
teraz dodajmy własny konstruktor do. Będzie on określał earLength
oprócz name
:
UPS!, Mamy błąd. Teraz nie możemy tworzyć królików. Co poszło nie tak?
krótka odpowiedź brzmi:
- konstruktory w klasach dziedziczących muszą wywołać
super(...)
I (!) zrób to przed użyciemthis
.
… ale dlaczego? Co tu się dzieje? Rzeczywiście, wymóg wydaje się dziwny.
oczywiście jest wyjaśnienie. Przejdźmy do szczegółów, żebyś naprawdę zrozumiał, o co chodzi.
w JavaScript istnieje rozróżnienie między funkcją konstruktora dziedziczącej klasy (tzw. „konstruktor Pochodny”) a innymi funkcjami., Konstruktor Pochodny ma specjalną właściwość wewnętrzną ]:"derived"
. To specjalna wewnętrzna etykieta.
Ta etykieta wpływa na jej zachowanie za pomocąnew
.
- gdy zwykła funkcja jest wykonywana z
new
, tworzy pusty obiekt i przypisuje go dothis
. - ale gdy uruchamiany jest pochodny konstruktor, nie robi tego. Oczekuje, że konstruktor-rodzic wykona to zadanie.,
tak więc konstruktor Pochodny musi wywołać super
w celu wykonania swojego konstruktora nadrzędnego (bazowego), w przeciwnym razie obiekt this
nie zostanie utworzony. I będzie błąd.
aby konstruktor Rabbit
działał, musi wywołać super()
przed użyciem this
, jak tutaj:
nadpisywanie pól klasy: trudna notatka
ta notatka zakłada, że masz pewne doświadczenie z klasami, być może w innych językach programowania.,
zapewnia lepszy wgląd w język, a także wyjaśnia zachowanie, które może być źródłem błędów (ale nie bardzo często).
Jeśli okaże się to trudne do zrozumienia, po prostu kontynuuj, Czytaj dalej, a następnie wróć do niego jakiś czas później.
możemy nadpisać nie tylko metody, ale także pola klas.
Chociaż, istnieje trudne zachowanie, gdy uzyskujemy dostęp do nadpisanego pola w konstruktorze nadrzędnym, zupełnie inne niż większość innych języków programowania.,
rozważ ten przykład:
tutaj, KlasaRabbit
rozszerzaAnimal
I nadpisujename
pole z własną wartością.
nie ma własnego konstruktora w, więcAnimal
konstruktor jest wywoływany.
interesujące jest to, że w obu przypadkach: new Animal()
I new Rabbit()
, alert
w linii (*)
pokazuje animal
.,
innymi słowy, konstruktor nadrzędny zawsze używa własnej wartości pola, a nie nadpisanej.
co w tym dziwnego?
Jeśli jeszcze nie jest jasne, Proszę porównać z metodami.
tutaj jest ten sam kod, ale zamiast this.name
pole wywołujemy this.showName()
metoda:
Uwaga: teraz wyjście jest inne.
i tego naturalnie oczekujemy. Gdy konstruktor nadrzędny jest wywoływany w klasie pochodnej, używa metody overridden.
… ale dla pól klasowych tak nie jest. Jak już wspomniano, konstruktor nadrzędny zawsze używa pola nadrzędnego.,
Dlaczego jest różnica?
cóż, powodem jest kolejność inicjalizacji w polu. Pole class jest inicjalizowane:
- przed konstruktorem dla klasy bazowej (która niczego nie rozszerza),
- bezpośrednio po
super()
dla klasy pochodnej.
w naszym przypadku jest klasą pochodną. Nie ma w nim constructor()
. Jak wspomniano wcześniej, jest to tak samo, jak gdyby istniał pusty konstruktor z tylko super(...args)
.,
tak więcnew Rabbit()
wywołujesuper()
, wykonując konstruktor nadrzędny i (zgodnie z regułą dla klas pochodnych) dopiero po zainicjowaniu pól klas. W czasie wykonywania konstruktora nadrzędnego nie ma jeszcze pól klasy Rabbit
, dlatego używane są pola Animal
.
ta subtelna różnica między polami a metodami jest specyficzna dla JavaScript
na szczęście to zachowanie ujawnia się tylko wtedy, gdy nadpisane pole jest używane w konstruktorze nadrzędnym., Wtedy może być trudno zrozumieć, co się dzieje, więc wyjaśniamy to tutaj.
Jeśli stanie się to problemem, można go naprawić za pomocą metod lub getterów/setterów zamiast pól.
Super: internals,]
Jeśli czytasz tutorial po raz pierwszy – ta sekcja może zostać pominięta.
chodzi o wewnętrzne mechanizmy dziedziczenia i super
.
wejdźmy trochę głębiej pod maskęsuper
. Po drodze zobaczymy kilka ciekawych rzeczy.,
Po pierwsze, z tego wszystkiego, czego się do tej pory nauczyliśmy, super
w ogóle nie działa!
tak, rzeczywiście, zadajmy sobie pytanie, Jak to technicznie powinno działać? Gdy metoda obiektu jest uruchomiona, otrzymuje on bieżący obiekt jako this
. Jeśli wywołamy super.method()
, to silnik musi pobrać method
z prototypu bieżącego obiektu. Ale jak?
zadanie może wydawać się proste, ale tak nie jest., Silnik zna bieżący obiekt this
, więc może uzyskać rodzica method
jakothis.__proto__.method
. Niestety, takie „naiwne” rozwiązanie nie zadziała.
zademonstrujmy problem. Bez klas, używając prostych obiektów dla uproszczenia.
możesz pominąć tę część i przejść poniżej do podsekcji]
, jeśli nie chcesz znać szczegółów. To nie zaszkodzi. Lub czytaj dalej, jeśli jesteś zainteresowany dogłębnym zrozumieniem rzeczy.
w poniższym przykładzie, rabbit.__proto__ = animal
., Teraz spróbujmy: w rabbit.eat()
wywołamy animal.eat()
, używając this.__proto__
:
w linii (*)
bierzemy eat
z prototypu (animal
) I wywoła go w kontekście bieżącego obiektu. Należy pamiętać, że .call(this)
jest tutaj ważne, ponieważ prostythis.__proto__.eat()
wykonałby rodzicaeat
w kontekście prototypu, a nie bieżącego obiektu.,
a w powyższym kodzie faktycznie działa zgodnie z przeznaczeniem: mamy poprawny alert
.
teraz dodajmy jeszcze jeden obiekt do łańcucha. Zobaczymy jak będzie:
Kod już nie działa! Widzimy błąd podczas próby wywołania longEar.eat()
.
może to nie jest takie oczywiste, ale jeśli prześledzimy longEar.eat()
wywołanie, to zobaczymy dlaczego. W obu wierszach (*)
I (**)
wartość this
jest bieżącym obiektem (longEar
)., To jest istotne: wszystkie metody obiektu otrzymują bieżący obiekt jako this
, a nie jako prototyp czy coś takiego.
oto obraz tego, co się dzieje:
problem nie może być rozwiązany za pomocą this
samodzielnie.
]
aby zapewnić rozwiązanie, JavaScript dodaje jeszcze jedną specjalną wewnętrzną właściwość dla funkcji:]
.,
Gdy funkcja jest określona jako metoda klasy lub obiektu, jej właściwość]
staje się tym obiektem.
następniesuper
używa go do rozwiązywania prototypu nadrzędnego i jego metod.
zobaczmy, jak to działa, najpierw przy zwykłych obiektach:
metody nie są „wolne”
jak już wcześniej wiedzieliśmy, ogólnie funkcje są „wolne”, a nie związane z obiektami w JavaScript. Można je więc kopiować między obiektami i wywoływać za pomocą innego this
.,
samo istnienie ]
narusza tę zasadę, ponieważ metody zapamiętują swoje obiekty. ]
nie można zmienić, więc ta więź jest na zawsze.
jedynym miejscem w języku, w którym jest używany]
– jestsuper
. Tak więc, jeśli metoda nie używa super
, to nadal możemy uznać ją za wolną i kopiować między obiektami. Ale z super
rzeczy mogą pójść nie tak.,
oto demo błędnegosuper
wynik po skopiowaniu:
wywołanietree.sayHi()
pokazuje „jestem zwierzęciem”. Zdecydowanie źle.
powód jest prosty:
oto schemat tego, co się dzieje:
metody, a nie Właściwości funkcji
różnica może być dla nas nieistotna, ale jest ważna dla JavaScript.
w poniższym przykładzie do porównania użyto składni innej niż metoda., ]
właściwość nie jest ustawiona i dziedziczenie nie działa:
podsumowanie
- aby rozszerzyć klasę:
class Child extends Parent
:- oznacza to, że
Child.prototype.__proto__
będzieParent.prototype
, więc metody są dziedziczone.
- oznacza to, że
- podczas nadpisywania konstruktora:
- musimy wywołać konstruktor nadrzędny jako
super()
wChild
konstruktor przed użyciemthis
.,
- musimy wywołać konstruktor nadrzędny jako
- podczas nadpisywania innej metody:
- możemy użyć
super.method()
wChild
metody do wywołaniaParent
metody.
- możemy użyć
- wewnętrzne:
- metody zapamiętują swoją klasę/obiekt w wewnętrznej
]
właściwość. W ten sposóbsuper
rozwiązuje metody nadrzędne. - dlatego nie jest bezpieczne kopiowanie metody z
super
z jednego obiektu do drugiego.,
- metody zapamiętują swoją klasę/obiekt w wewnętrznej
również:
- funkcje strzałek nie mają własnych
this
lubsuper
, więc w przejrzysty sposób pasują do otaczającego kontekstu.
Dodaj komentarz