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.,

Dowolne wyrażenie jest dozwolone poextends

składnia klasy pozwala określić nie tylko klasę, ale każde wyrażenie po 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 Userdziedziczy 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 stopmetoda, 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życiem this.

… 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 znew, 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

zaawansowana 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,]

zaawansowane informacje

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 methodjakothis.__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

  1. aby rozszerzyć klasę: class Child extends Parent:
    • oznacza to, że Child.prototype.__proto__ będzie Parent.prototype, więc metody są dziedziczone.
  2. podczas nadpisywania konstruktora:
    • musimy wywołać konstruktor nadrzędny jakosuper() wChild konstruktor przed użyciemthis.,
  3. podczas nadpisywania innej metody:
    • możemy użyćsuper.method()wChildmetody do wywołaniaParent metody.
  4. wewnętrzne:
    • metody zapamiętują swoją klasę/obiekt w wewnętrznej] właściwość. W ten sposób super rozwiązuje metody nadrzędne.
    • dlatego nie jest bezpieczne kopiowanie metody z super z jednego obiektu do drugiego.,

również:

  • funkcje strzałek nie mają własnychthis lubsuper, więc w przejrzysty sposób pasują do otaczającego kontekstu.