Korzyści dziedziczenia prototypowego nad klasycznymi?

Więc w końcu przestałem przeciągać stopy przez te wszystkie lata i postanowiłem nauczyć się JavaScript "poprawnie". Jednym z najważniejszych elementów konstrukcji języków jest implementacja dziedziczenia. Mając doświadczenie w Rubim, byłem naprawdę szczęśliwy widząc zamknięcia i dynamiczne pisanie; ale na całe życie nie mogę zrozumieć, jakie korzyści można uzyskać z instancji obiektowych przy użyciu innych instancji do dziedziczenia.

Author: HerbalMart, 2010-05-10

5 answers

Wiem, że ta odpowiedź jest spóźniona o 3 lata, ale naprawdę myślę, że obecne odpowiedzi nie dostarczają wystarczających informacji o jak dziedziczenie prototypowe jest lepsze niż klasyczne dziedziczenie .

Najpierw zobaczmy najczęstsze argumenty programistów JavaScript w obronie dziedziczenia prototypowego (biorę te argumenty z bieżącej puli odpowiedzi):

    To proste. Jest potężna.
  1. prowadzi do mniejszych, mniej zbędnych kod.
  2. jest dynamiczny i dlatego jest lepszy dla języków dynamicznych.

Teraz wszystkie te argumenty są słuszne, ale nikt nie zadał sobie trudu wyjaśnienia dlaczego. To tak, jakby powiedzieć dziecku, że nauka matematyki jest ważna. Oczywiście, że jest, ale dziecko na pewno nie obchodzi; i nie można zrobić dziecko jak matematyka, mówiąc, że to ważne.

Myślę, że problem z dziedziczeniem prototypowym polega na tym, że jest on wyjaśniony z perspektywy JavaScript. Uwielbiam JavaScript, ale dziedziczenie prototypowe w JavaScript jest błędne. W przeciwieństwie do klasycznego dziedziczenia istnieją dwa wzory dziedziczenia prototypowego:

  1. prototypowy wzór dziedziczenia prototypów.
  2. wzorzec konstruktora dziedziczenia prototypowego.

Niestety JavaScript wykorzystuje wzorzec konstruktora dziedziczenia prototypowego. To dlatego, że kiedy JavaScript został stworzony, Brendan Eich (twórca JS) chciał, aby wyglądał jak Java (która ma Klasyczne "dziedziczenie": {]}

I jako młodszy brat pchaliśmy go do Javy, jako język uzupełniający, taki jak Visual Basic, był do C++ w rodzinach językowych Microsoftu w tym czasie.

Jest to złe, ponieważ kiedy ludzie używają konstruktorów w JavaScript, myślą o konstruktorach dziedziczących po innych konstruktorach. To jest złe. W dziedziczeniu prototypowym obiekty dziedziczą od innych obiektów. Konstruktorzy nigdy nie wchodzą w grę. To jest to, co najbardziej myli osób.

Ludzie z języków takich jak Java, która ma Klasyczne dziedziczenie, stają się jeszcze bardziej zdezorientowani, ponieważ chociaż konstruktory wyglądają jak Klasy, nie zachowują się jak Klasy. Jak powiedziałDouglas Crockford:

Miało to na celu uczynienie języka bardziej znanym dla klasycznie wyszkolonych programistów, ale nie udało się tego zrobić, jak widać z bardzo niskiej opinii programistów Javy o JavaScript. JavaScript ' s constructor pattern did nie przemawia do klasycznego tłumu. Zasłonił również prawdziwy prototypowy charakter JavaScript. W rezultacie jest bardzo niewielu programistów, którzy wiedzą, jak efektywnie korzystać z języka.

Proszę bardzo. Prosto z paszczy konia.

Prawdziwe Dziedziczenie Prototypowe

Dziedziczenie prototypów polega na obiektach. Obiekty dziedziczą właściwości od innych obiektów. To wszystko. Istnieją dwa sposoby tworzenia obiektów za pomocą prototypu dziedziczenie:

  1. Utwórz zupełnie nowy obiekt.
  2. Sklonuj istniejący obiekt i rozszerz go.

Uwaga: JavaScript oferuje dwa sposoby klonowania obiektu- delegation i concatenation . Odtąd będę używał słowa "klon", aby odnosić się wyłącznie do dziedziczenia przez delegację, a słowo "Kopia", aby odnosić się wyłącznie do dziedziczenia przez konkatenację.

Dość gadania. Zobaczmy kilka przykładów. Say I have a circle of radius 5:
var circle = {
    radius: 5
};

Możemy obliczyć pole i obwód okręgu z jego promienia:

circle.area = function () {
    var radius = this.radius;
    return Math.PI * radius * radius;
};

circle.circumference = function () {
    return 2 * Math.PI * this.radius;
};

Teraz chcę utworzyć kolejny okrąg o promieniu 10. Jednym ze sposobów na to jest:

var circle2 = {
    radius: 10,
    area: circle.area,
    circumference: circle.circumference
};

Jednak JavaScript zapewnia lepszy sposób - delegacja . Crockford ' s Object.create do tego celu służy funkcja:

var circle2 = Object.create(circle);
circle2.radius = 10;
To wszystko. Właśnie zrobiłeś prototypowe dziedziczenie w JavaScript. Czy to nie było proste? Bierzesz obiekt, sklonujesz go, zmieniasz cokolwiek potrzebujesz, i hej presto-masz nowy przedmiot. Teraz możecie zapytać: "Jak to jest proste? Za każdym razem, gdy chcę utworzyć nowy okrąg, muszę sklonować circle i ręcznie przypisać mu promień". Cóż, rozwiązaniem jest użycie funkcji do wykonywania ciężkiego podnoszenia za Ciebie: {]}
function createCircle(radius) {
    var newCircle = Object.create(circle);
    newCircle.radius = radius;
    return newCircle;
}

var circle2 = createCircle(10);

W rzeczywistości możesz połączyć to wszystko w jeden obiekt dosłowny w następujący sposób:

var circle = {
    radius: 5,
    create: function (radius) {
        var circle = Object.create(this);
        circle.radius = radius;
        return circle;
    },
    area: function () {
        var radius = this.radius;
        return Math.PI * radius * radius;
    },
    circumference: function () {
        return 2 * Math.PI * this.radius;
    }
};

var circle2 = circle.create(10);

Dziedziczenie prototypowe w JavaScript

Jeśli zauważysz w powyższym programuje funkcję create tworząc klon circle, przypisując jej nową radius, a następnie ją zwraca. Jest to dokładnie to, co robi konstruktor w JavaScript:

function Circle(radius) {
    this.radius = radius;
}

Circle.prototype.area = function () {
    var radius = this.radius;
    return Math.PI * radius * radius;
};

Circle.prototype.circumference = function () {         
    return 2 * Math.PI * this.radius;
};

var circle = new Circle(5);
var circle2 = new Circle(10);

Wzorzec konstruktora w JavaScript jest wzorcem prototypowym odwróconym. Zamiast tworzenia obiektu tworzysz konstruktor. Słowo kluczowe new wiąże wskaźnik this wewnątrz konstruktora z klonem prototype konstruktora.

Brzmi myląco? To dlatego, że konstruktor wzorzec w JavaScript niepotrzebnie komplikuje sprawy. To jest to, co większość programistów uważa za trudne do zrozumienia.

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.

Jest cała masa innych powodów, dla których wzorzec konstruktora w JavaScript powinien być unikany. Możecie o nich przeczytać w moim wpisie na blogu tutaj: konstruktorzy vs prototypy


Więc jakie są korzyści dziedziczenia prototypowego nad dziedziczeniem klasycznym? Przejdźmy jeszcze raz do najczęstszych argumentów i wyjaśnijmy dlaczego.

1. Dziedziczenie prototypowe jest proste

CMS stwierdza w swojej odpowiedzi:

Moim zdaniem główną zaletą dziedziczenia prototypowego jest jego prostota.

Zastanówmy się, co właśnie zrobiliśmy. Stworzyliśmy obiekt circle o promieniu 5. Następnie sklonowaliśmy go i daliśmy Sklonuj promień 10. Dlatego potrzebujemy tylko dwóch rzeczy, aby prototypowe dziedziczenie działało:]}
  1. sposób tworzenia nowego obiektu (np. literały obiektów).
  2. sposób rozszerzenia istniejącego obiektu(np. Object.create).

W przeciwieństwie do klasycznego dziedziczenia jest znacznie bardziej skomplikowane. W dziedziczeniu klasycznym masz:

  1. klasy.
  2. obiekt.
  3. Interfejsy.
  4. Klasy Abstrakcyjne.
  5. finał Klasy.
  6. Wirtualne Klasy Bazowe.
  7. konstruktorzy.
  8. Destruktory.
Rozumiesz. Chodzi o to, że dziedziczenie prototypowe jest łatwiejsze do zrozumienia, łatwiejsze do wdrożenia i łatwiejsze do rozumowania.

Jak to ujął Steve Yegge w swoim klasycznym poście na blogu " Portret N00b":

Metadane to każdy rodzaj opisu lub modelu czegoś innego. Komentarze w Twoim kodzie to tylko opis w języku naturalnym z obliczeń. To, co sprawia, że metadane metadane nie są absolutnie konieczne. Jeśli mam psa z rodowodem, a gubię papiery, nadal mam psa doskonale ważnego.

W tym samym sensie klasy są tylko meta-danymi. Klasy nie są ściśle wymagane do dziedziczenia. Jednak niektórzy ludzie (zwykle n00bs) uważają zajęcia za bardziej komfortowe w pracy. Daje im fałszywe poczucie bezpieczeństwa.

Cóż, wiemy również, że typy statyczne są tylko metadane. To specjalistyczny rodzaj komentarzy adresowanych do dwóch rodzajów czytelników: programistów i kompilatorów. Typy statyczne opowiadają historię o obliczeniach, prawdopodobnie aby pomóc obu grupom czytelników zrozumieć intencje programu. Ale typy statyczne można wyrzucić w czasie wykonywania, ponieważ w końcu są to tylko stylizowane komentarze. Są jak Rodowodowe papiery. może to sprawić, że pewien niepewny typ osobowości będzie szczęśliwszy ze swoim psem, ale pies na pewno nie care.

Jak powiedziałem wcześniej, zajęcia dają ludziom fałszywe poczucie bezpieczeństwa. Na przykład dostajesz za dużo NullPointerExceptions w Javie, nawet jeśli twój 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 środowiska twierdzi, że Klasyczne dziedziczenie jest potężniejsze niż dziedziczenie prototypowe, ponieważ ma:

  1. zmienne prywatne.
  2. dziedziczenie wielokrotne.

To twierdzenie jest fałszywe. Wiemy już, że JavaScript obsługuje prywatne zmienne poprzez zamknięcie , ale co z dziedziczeniem wielokrotnym? Obiekty w JavaScript mają tylko jeden prototyp.

Prawda jest taka, że dziedziczenie prototypowe obsługuje dziedziczenie z wielu prototypów. Dziedziczenie prototypowe oznacza po prostu, że jeden obiekt dziedziczy po innym obiekt. W rzeczywistości istnieją dwa sposoby implementacji dziedziczenia prototypowego :

  1. Delegacja lub dziedziczenie różnicowe
  2. klonowanie lub dziedziczenie Konkatenacyjne

Tak JavaScript zezwala tylko na delegowanie obiektów do jednego innego obiektu. Umożliwia jednak kopiowanie właściwości dowolnej liczby obiektów. Na przykład _.extend czy tylko to.

Oczywiście wielu programistów nie uważa tego za prawdziwe dziedziczenie ponieważ instanceof oraz isPrototypeOf powiedz co innego. Jednak można to łatwo naprawić, przechowując tablicę prototypów na każdym obiekcie, który dziedziczy z prototypu poprzez konkatenację: {]}

function copyOf(object, prototype) {
    var prototypes = object.prototypes;
    var prototypeOf = Object.isPrototypeOf;
    return prototypes.indexOf(prototype) >= 0 ||
        prototypes.some(prototypeOf, prototype);
}

Stąd dziedziczenie prototypowe jest tak samo potężne jak dziedziczenie Klasyczne. W rzeczywistości jest to znacznie potężniejsze niż klasyczne dziedziczenie, ponieważ w dziedziczeniu prototypowym możesz ręcznie wybrać, które właściwości skopiować, a które właściwości pominąć z różnych prototypy.

W dziedziczeniu klasycznym nie jest możliwe (a przynajmniej bardzo trudne) wybranie właściwości, które chcesz dziedziczyć. Używają wirtualnych klas bazowych i interfejsów do rozwiązania problemu diamentowego.

W JavaScript jednak najprawdopodobniej nigdy nie usłyszysz o problemie diamond, ponieważ możesz kontrolować dokładnie, które właściwości chcesz dziedziczyć i od których prototypów.

3. Dziedziczenie prototypowe jest mniej zbędne

Ten punkt jest trochę trudniejsze do wyjaśnienia, ponieważ Klasyczne dziedziczenie niekoniecznie prowadzi do większej ilości zbędnego kodu. W rzeczywistości dziedziczenie, czy to klasyczne, czy prototypowe, jest używane do zmniejszenia nadmiarowości w kodzie.

Jednym z argumentów może być to, że większość języków programowania z dziedziczeniem klasycznym jest typowana statycznie i wymaga od użytkownika jawnego deklarowania typów (w przeciwieństwie do Haskella, który ma niejawne typowanie statyczne). Stąd prowadzi to do bardziej wyrazistego kodu.

Java jest znana z takie zachowanie. Wyraźnie pamiętam Bob Nystrom w swoim wpisie na blogu o Pratt Parsers:

You gotta love Java 'S" please sign it in quadruplicate " level of biurokration here.

Ponownie, myślę, że to tylko dlatego, że Java tak bardzo ssie.

Jednym z ważnych argumentów jest to, że nie wszystkie języki, które mają Klasyczne dziedziczenie, wspierają dziedziczenie wielokrotne. Znowu przychodzi mi do głowy Java. Tak Java ma interfejsy, ale to nie wystarczy. Czasami naprawdę potrzebujesz wielokrotnego dziedziczenia.

Ponieważ dziedziczenie prototypowe pozwala na dziedziczenie wielokrotne, kod, który wymaga dziedziczenia wielokrotnego, jest mniej zbędny, jeśli jest napisany przy użyciu dziedziczenia prototypowego, a nie w języku, który ma Klasyczne dziedziczenie, ale nie ma dziedziczenia wielokrotnego.

4. Dziedziczenie prototypowe jest dynamiczne

Jedną z najważniejszych zalet dziedziczenia prototypowego jest możliwość dodawania nowych właściwości do prototypów po ich utworzeniu. Pozwala to na dodanie nowych metod do prototypu, które zostaną automatycznie udostępnione wszystkim obiektom, które delegują do tego prototypu.

Nie jest to możliwe w dziedziczeniu klasycznym, ponieważ po utworzeniu klasy nie można jej modyfikować w czasie wykonywania. Jest to prawdopodobnie największa zaleta dziedziczenia prototypowego nad dziedziczeniem klasycznym i powinna być na szczycie. Jednak najbardziej lubię oszczędzać na koniec.

Podsumowanie

Sprawy dziedziczenia prototypowego. Ważne jest, aby edukować programistów JavaScript, dlaczego porzucić wzorzec konstruktora dziedziczenia prototypowego na rzecz prototypowego wzorca dziedziczenia prototypowego.

Musimy zacząć poprawnie uczyć JavaScript, a to oznacza pokazanie nowym programistom, jak pisać kod używając wzorca prototypowego zamiast wzorca konstruktora.

Nie tylko będzie łatwiej wyjaśnić dziedziczenie prototypowe przy użyciu wzorca prototypowego, ale będzie to również lepsze programistów.

Jeśli spodobała ci się ta odpowiedź, powinieneś również przeczytać mój wpis na blogu "dlaczego dziedziczenie prototypowe ma znaczenie". Zaufaj mi, nie będziesz rozczarowany.

 506
Author: Aadit M Shah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-06 13:01:32

Pozwól mi odpowiedzieć na pytanie w linii.

Dziedziczenie prototypu ma następujące zalety:

  1. lepiej nadaje się do języków dynamicznych, ponieważ dziedziczenie jest tak dynamiczne, jak środowisko, w którym się znajduje. (Zastosowanie do JavaScript powinno być tutaj oczywiste.) Pozwala to na szybkie robienie rzeczy w locie, takich jak dostosowywanie klas bez ogromnej ilości kodu infrastruktury.
  2. łatwiej jest zaimplementować schemat obiektu prototypowego niż klasyczne schematy dychotomii Klasa / obiekt.
  3. eliminuje potrzebę stosowania skomplikowanych ostrych krawędzi wokół modelu obiektowego, takich jak "metaklasy" (nigdy metaklasy nie lubiłem... przepraszam!) lub "wartości własne" lub tym podobne.

Ma jednak następujące wady:

  1. Sprawdzenie języka prototypu nie jest niemożliwe, ale jest bardzo, bardzo trudne. Większość" sprawdzania typu "języków prototypowych jest czystymi kontrolami typu "Duck typing". To nie jest odpowiednie dla wszystkich środowisk.
  2. podobnie trudno jest robić rzeczy takie jak optymalizacja metody wysyłania przez statyczne(lub, często, nawet dynamiczne!) analiza. To Może (podkreślam: Może ) być bardzo nieefektywne bardzo łatwo.
  3. Podobnie tworzenie obiektów może być (i zazwyczaj jest) znacznie wolniejsze w języku prototypów niż w bardziej konwencjonalnym schemacie dychotomii Klasa / obiekt.

Myślę, że można czytać między wierszami powyżej i wymyślić odpowiednie zalety i wady tradycyjnych schematów klas/obiektów. Oczywiście jest ich więcej w każdym obszarze, więc resztę zostawię innym.

 34
Author: JUST MY correct OPINION,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-10 08:11:04

IMO główną zaletą dziedziczenia prototypowego jest jego prostota.

Prototypowa natura języka może mylić 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 tworzyć 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 klas...

Dziedziczenie prototypowe będzie w niedalekiej przyszłości znacznie bardziej popularne, Specyfikacja ECMAScript 5th Edition wprowadziła Object.create metoda, która pozwala na wytworzenie nowej instancji obiektu, która dziedziczy po innej w bardzo prosty sposób:

var obj = Object.create(baseInstance);

Ta nowa wersja standardu jest wdrażana przez wszystkich dostawców przeglądarek i myślę, że zaczniemy widzieć bardziej czyste dziedziczenie prototypowe...

 26
Author: CMS,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-11 11:11:31

Naprawdę nie ma wiele do wyboru między tymi dwoma metodami. Podstawową ideą do zrozumienia jest to, że gdy silnik JavaScript otrzymuje właściwość obiektu do odczytu, najpierw sprawdza instancję, a jeśli jej brakuje, sprawdza łańcuch prototypów. Oto przykład, który pokazuje różnicę między prototypowym a klasycznym:

Prototypowy

var single = { status: "Single" },
    princeWilliam = Object.create(single),
    cliffRichard = Object.create(single);

console.log(Object.keys(princeWilliam).length); // 0
console.log(Object.keys(cliffRichard).length); // 0

// Marriage event occurs
princeWilliam.status = "Married";

console.log(Object.keys(princeWilliam).length); // 1 (New instance property)
console.log(Object.keys(cliffRichard).length); // 0 (Still refers to prototype)

Klasyczna z metodami instancji (nieefektywna, ponieważ każda instancja przechowuje własne property)

function Single() {
    this.status = "Single";
}

var princeWilliam = new Single(),
    cliffRichard = new Single();

console.log(Object.keys(princeWilliam).length); // 1
console.log(Object.keys(cliffRichard).length); // 1

Efektywna klasyczna

function Single() {
}

Single.prototype.status = "Single";

var princeWilliam = new Single(),
    cliffRichard = new Single();

princeWilliam.status = "Married";

console.log(Object.keys(princeWilliam).length); // 1
console.log(Object.keys(cliffRichard).length); // 0
console.log(cliffRichard.status); // "Single"

Jak widzisz, ponieważ możliwe jest manipulowanie prototypem" klas " zadeklarowanych w stylu klasycznym, nie ma naprawdę żadnej korzyści z używania dziedziczenia prototypowego. Jest podzbiorem metody klasycznej.

 9
Author: Noel Abrahams,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-20 12:59:43

Tworzenie stron www: dziedziczenie prototypowe a dziedziczenie Klasyczne

Http://chamnapchhorn.blogspot.com/2009/05/prototypal-inheritance-vs-classical.html

Dziedziczenie Klasyczne Vs prototypowe-przepełnienie stosu

Dziedziczenie Klasyczne Vs prototypowe

 2
Author: ratty,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:50