W końcu przestałem ciągnąć stopy przez te wszystkie lata i postanowiłem nauczyć się JavaScript „poprawnie”. Jednym z najbardziej porywających elementów projektowania języków jest implementacja dziedziczenia. Mając doświadczenie w Ruby, bardzo cieszyłem się z zamykania i dynamicznego pisania; ale przez całe życie nie mogę zrozumieć, jakie korzyści można uzyskać z instancji obiektów wykorzystujących inne instancje do dziedziczenia.
271
Odpowiedzi:
Wiem, że ta odpowiedź jest spóźniona o 3 lata, ale naprawdę uważam, że obecne odpowiedzi nie dostarczają wystarczających informacji na temat tego, w jaki sposób dziedziczenie prototypowe jest lepsze niż dziedziczenie klasyczne .
Najpierw zobaczmy najczęstsze argumenty, które programiści JavaScript twierdzą w obronie dziedziczenia prototypowego (biorę te argumenty z bieżącej puli odpowiedzi):
Teraz wszystkie te argumenty są prawidłowe, ale nikt nie zadał sobie trudu, aby wyjaśnić, dlaczego. To tak, jakby powiedzieć dziecku, że nauka matematyki jest ważna. Jasne, że tak, ale dziecku na pewno to nie obchodzi; i nie możesz zrobić dziecka takiego jak matematyka, mówiąc, że to ważne.
Myślę, że problem z dziedziczeniem prototypów polega na tym, że wyjaśniono go z perspektywy JavaScript. Uwielbiam JavaScript, ale prototypowe dziedziczenie w JavaScript jest złe. W przeciwieństwie do klasycznego dziedziczenia istnieją dwa wzorce dziedziczenia prototypowego:
Niestety JavaScript wykorzystuje wzorzec konstruktora dziedziczenia prototypowego. Dzieje się tak dlatego, że kiedy JavaScript został utworzony, Brendan Eich (twórca JS) chciał, aby wyglądał jak Java (która ma klasyczne dziedzictwo):
Jest to złe, ponieważ kiedy ludzie używają konstruktorów w JavaScript, myślą o konstruktorach dziedziczących po innych konstruktorach. To jest źle. W prototypowym dziedzictwie obiekty dziedziczą po innych obiektach. Konstruktorzy nigdy nie pojawiają się na zdjęciu. To myli większość ludzi.
Ludzie z języków takich jak Java, która ma klasyczne dziedzictwo, stają się jeszcze bardziej zdezorientowani, ponieważ chociaż konstruktory wyglądają jak klasy, nie zachowują się jak klasy. Jak stwierdził Douglas Crockford :
Masz to. Prosto z pyska konia.
Prawdziwe dziedzictwo prototypowe
Dziedzictwo prototypowe dotyczy przede wszystkim przedmiotów. Obiekty dziedziczą właściwości z innych obiektów. To wszystko. Istnieją dwa sposoby tworzenia obiektów przy użyciu dziedziczenia prototypowego:
Uwaga: JavaScript oferuje dwa sposoby klonowania obiektu - delegowanie i konkatenacja . Odtąd będę używać słowa „klon”, aby odnosić się wyłącznie do dziedziczenia poprzez przekazanie, a słowa „kopiować”, aby odnosić się wyłącznie do dziedziczenia poprzez konkatenację.
Dość gadania. Zobaczmy kilka przykładów. Powiedz, że mam okrąg o promieniu
5
:Możemy obliczyć powierzchnię i obwód koła z jego promienia:
Teraz chcę utworzyć kolejny okrąg o promieniu
10
. Jednym ze sposobów na to byłoby:Jednak JavaScript zapewnia lepszy sposób - delegowanie .
Object.create
Funkcja ta jest wykorzystywana w tym celu:To wszystko. Właśnie zrobiłeś prototypowe dziedziczenie w JavaScript. Czy to nie było proste? Bierzesz przedmiot, klonujesz go, zmieniasz wszystko, czego potrzebujesz, i hej presto - masz nowy obiekt.
Teraz możesz zapytać: „Jak to jest proste? Za każdym razem, gdy chcę utworzyć nowy okrąg, muszę go sklonować
circle
i ręcznie przypisać mu promień”. Cóż, rozwiązaniem jest użycie funkcji do wykonania ciężkiego podnoszenia:W rzeczywistości możesz to wszystko połączyć w jeden dosłowny obiekt w następujący sposób:
Dziedziczenie prototypowe w JavaScript
Jeśli zauważysz w powyższym programie,
create
funkcja tworzy kloncircle
, przypisujeradius
do niego nowy , a następnie zwraca go. To właśnie robi konstruktor w JavaScript:Wzorzec konstruktora w JavaScript jest odwróconym wzorcem prototypowym. Zamiast tworzyć obiekt, tworzysz konstruktor.
new
Kluczowe wiążethis
kursor wewnątrz konstruktora do klonemprototype
konstruktora.Brzmi myląco? Jest tak, ponieważ wzorzec konstruktora w JavaScript niepotrzebnie komplikuje rzeczy. Trudno to zrozumieć większości programistów.
Zamiast myśleć o obiektach dziedziczących po innych obiektach, myślą o konstruktorach dziedziczących po innych konstruktorach, a następnie stają się całkowicie zdezorientowani.
Istnieje wiele innych powodów, dla których należy unikać wzorca konstruktora w JavaScript. Możesz o nich przeczytać w moim blogu tutaj: Konstruktory kontra prototypy
Jakie są zatem zalety dziedziczenia prototypowego w porównaniu z dziedziczeniem klasycznym? Ponownie przejrzyjmy najczęstsze argumenty i wyjaśnijmy dlaczego .
1. Dziedziczenie prototypowe jest proste
CMS stwierdza w swojej odpowiedzi:
Zastanówmy się, co właśnie zrobiliśmy. Stworzyliśmy obiekt
circle
o promieniu5
. Następnie sklonowaliśmy go i nadaliśmy klonowi promień10
.Dlatego potrzebujemy tylko dwóch rzeczy, aby dziedziczenie prototypowe działało:
Object.create
.).Natomiast klasyczne dziedziczenie jest znacznie bardziej skomplikowane. W dziedzictwie klasycznym masz:
Masz pomysł. Chodzi o to, że dziedziczenie prototypów jest łatwiejsze do zrozumienia, łatwiejsze do wdrożenia i łatwiejsze do uzasadnienia.
Jak pisze Steve Yegge w swoim klasycznym blogu „ Portrait of a N00b ”:
W tym samym sensie klasy są po prostu metadanymi. Klasy nie są ściśle wymagane do dziedziczenia. Jednak niektórzy ludzie (zwykle n00bs) uważają, że zajęcia są wygodniejsze w pracy. Daje im to fałszywe poczucie bezpieczeństwa.
Jak wspomniałem wcześniej, zajęcia dają ludziom fałszywe poczucie bezpieczeństwa. Na przykład
NullPointerException
w Javie jest za dużo s, nawet jeśli kod jest doskonale czytelny. Uważam, że klasyczne dziedziczenie zwykle przeszkadza w programowaniu, ale może to tylko Java. Python ma niesamowity klasyczny system dziedziczenia.2. Dziedziczenie prototypowe jest potężne
Większość programistów wywodzących się z klasycznego pochodzenia twierdzi, że klasyczne dziedziczenie jest potężniejsze niż dziedziczenie prototypowe, ponieważ:
To twierdzenie jest fałszywe. Wiemy już, że JavaScript obsługuje zmienne prywatne poprzez zamknięcia , ale co z wielokrotnym dziedziczeniem? Obiekty w JavaScript mają tylko jeden prototyp.
Prawda jest taka, że dziedziczenie prototypów obsługuje dziedziczenie po wielu prototypach. Dziedziczenie prototypowe oznacza po prostu, że jeden obiekt dziedziczy po innym obiekcie. Istnieją dwa sposoby implementacji dziedziczenia prototypowego :
Tak JavaScript pozwala jedynie na delegowanie obiektów do jednego innego obiektu. Pozwala to jednak skopiować właściwości dowolnej liczby obiektów. Na przykład
_.extend
właśnie to robi.Oczywiście wielu programistów nie uważa tego za dziedzictwo, ponieważ
instanceof
iisPrototypeOf
mówią inaczej. Można jednak łatwo temu zaradzić, przechowując tablicę prototypów na każdym obiekcie, który dziedziczy po prototypie przez konkatenację:Dlatego dziedziczenie prototypowe jest tak samo potężne jak dziedziczenie klasyczne. W rzeczywistości jest znacznie potężniejszy niż klasyczne dziedziczenie, ponieważ w dziedziczeniu prototypowym możesz ręcznie wybrać, które właściwości skopiować, a które pominąć w przypadku różnych prototypów.
W dziedziczeniu klasycznym nie można (lub przynajmniej bardzo trudno) wybrać właściwości, które chcesz dziedziczyć. Używają wirtualnych klas bazowych i interfejsów do rozwiązania problemu diamentów .
Jednak w JavaScript najprawdopodobniej nigdy nie usłyszysz o problemie z diamentem, ponieważ możesz dokładnie kontrolować, które właściwości chcesz dziedziczyć i od których prototypów.
3. Dziedziczenie prototypowe jest mniej zbędne
Ten punkt jest nieco trudniejszy do wyjaśnienia, ponieważ klasyczne dziedziczenie niekoniecznie prowadzi do bardziej zbędnego kodu. W rzeczywistości dziedziczenie, zarówno klasyczne, jak i prototypowe, stosuje się w celu zmniejszenia nadmiarowości kodu.
Jednym z argumentów może być to, że większość języków programowania z klasycznym dziedziczeniem jest typowana statycznie i wymaga od użytkownika jawnego deklarowania typów (w przeciwieństwie do Haskell, który ma niejawne typowanie statyczne). Stąd prowadzi to do bardziej pełnego kodu.
Java jest znana z tego zachowania. Wyraźnie pamiętam Boba Nystroma, który wspomniał o następującej anegdocie w swoim blogu na temat Pratta Parsera :
Ponownie myślę, że dzieje się tak tylko dlatego, że Java jest do bani.
Jednym z ważnych argumentów jest to, że nie wszystkie języki, które mają klasyczne dziedziczenie, obsługują wielokrotne dziedziczenie. Znowu przychodzi mi na myśl Java. Tak Java ma interfejsy, ale to nie wystarczy. Czasami naprawdę potrzebujesz wielokrotnego dziedziczenia.
Ponieważ dziedziczenie prototypowe pozwala na wielokrotne dziedziczenie, kod wymagający wielokrotnego dziedziczenia jest mniej zbędny, jeśli jest napisany przy użyciu dziedziczenia prototypowego, a nie w języku, który ma klasyczne dziedzictwo, ale nie ma wielokrotnego dziedziczenia.
4. Dziedziczenie prototypowe jest dynamiczne
Jedną z najważniejszych zalet dziedziczenia prototypów jest to, że możesz dodawać nowe właściwości do prototypów po ich utworzeniu. Umożliwia to dodawanie nowych metod do prototypu, który zostanie automatycznie udostępniony wszystkim obiektom, które delegują ten prototyp.
Nie jest to możliwe w przypadku klasycznego dziedziczenia, ponieważ po utworzeniu klasy nie można jej modyfikować w czasie wykonywania. Jest to prawdopodobnie największa zaleta dziedziczenia prototypowego w porównaniu z dziedziczeniem klasycznym i powinna być na szczycie. Lubię jednak oszczędzać to, co najlepsze na koniec.
Wniosek
Dziedziczenie prototypowe ma znaczenie. Ważne jest, aby informować programistów JavaScript, dlaczego należy porzucić konstruktorski wzorzec dziedziczenia prototypowego na rzecz prototypowego wzorca dziedziczenia prototypowego.
Musimy zacząć uczyć JavaScript poprawnie, co oznacza pokazanie nowym programistom, jak pisać kod przy użyciu wzorca prototypowego zamiast wzorca konstruktora.
Nie tylko łatwiej będzie wyjaśnić dziedziczenie prototypów za pomocą wzorca prototypowego, ale także sprawi, że będą lepsi programiści.
Jeśli podobała Ci się ta odpowiedź, powinieneś również przeczytać mój post na blogu „ Dlaczego dziedziczenie prototypowe ma znaczenie ”. Zaufaj mi, nie będziesz rozczarowany.
źródło
Object.create
tworzy nowy obiekt z określonym prototypem. Wybór słów sprawia wrażenie, że prototyp zostaje sklonowany.Pozwól mi faktycznie odpowiedzieć na pytanie w tekście.
Dziedziczenie prototypów ma następujące zalety:
Ma jednak następujące wady:
Myślę, że możesz czytać między wierszami powyżej i wymyślić odpowiednie zalety i wady tradycyjnych schematów klas / obiektów. W każdym obszarze jest oczywiście więcej, więc resztę pozostawiam innym osobom odpowiadającym.
źródło
IMO główną zaletą dziedziczenia prototypowego jest jego prostota.
Prototypowa natura języka może dezorientować ludzi, którzy są klasycznie wyszkoleni, ale okazuje się, że w rzeczywistości jest to naprawdę prosta i potężna koncepcja, dziedziczenie różnicowe .
Nie musisz dokonywać klasyfikacji , twój kod jest mniejszy, mniej zbędny, obiekty dziedziczą po innych, bardziej ogólnych obiektach.
Jeśli myślisz prototypowo , wkrótce zauważysz, że nie potrzebujesz zajęć ...
Dziedziczenie prototypów będzie znacznie bardziej popularne w najbliższej przyszłości, specyfikacja ECMAScript 5th Edition wprowadziła
Object.create
metodę, która pozwala na stworzenie nowej instancji obiektu, która dziedziczy po innej w naprawdę prosty sposób:Ta nowa wersja standardu jest wdrażana przez wszystkich dostawców przeglądarek i myślę, że zaczniemy widzieć bardziej czyste dziedziczenie prototypów ...
źródło
Tak naprawdę nie ma wiele do wyboru między tymi dwiema metodami. Podstawową ideą do uchwycenia jest to, że gdy silnik JavaScript otrzymuje właściwość obiektu do odczytu, najpierw sprawdza instancję, a jeśli tej właściwości brakuje, sprawdza łańcuch prototypów. Oto przykład, który pokazuje różnicę między prototypowym a klasycznym:
Prototypowy
Klasyczny z metodami instancji (nieefektywne, ponieważ każda instancja przechowuje swoją własność)
Wydajny klasyk
Jak widać, ponieważ można manipulować prototypem „klas” zadeklarowanych w stylu klasycznym, korzystanie z dziedziczenia prototypowego naprawdę nie przynosi korzyści. Jest to podzbiór metody klasycznej.
źródło
Tworzenie stron internetowych: dziedziczenie prototypowe a dziedziczenie klasyczne
http://chamnapchhorn.blogspot.com/2009/05/prototypal-inheritance-vs-classical.html
Prototypowe dziedziczenie klasycznych V - przepełnienie stosu
Prototypowe dziedzictwo klasycznego Vs
źródło