dziedziczenie klasyczne a dziedziczenie prototypowe w javascript

118

Przeszukałem w Google tak wiele linków i nie mogę dobrze zrozumieć różnicy między dziedziczeniem klasycznym a dziedziczeniem prototypowym?

Nauczyłem się z nich kilku rzeczy, ale nadal jestem zdezorientowany.

Dziedziczenie klasyczne

// Shape - superclass
function Shape() {
  this.x = 0;
  this.y = 0;
}

//superclass method
Shape.prototype.move = function(x, y) {
    this.x += x;
    this.y += y;
    console.info("Shape moved.");
};

// Rectangle - subclass
function Rectangle() {
  Shape.call(this); //call super constructor.
}

//subclass extends superclass
Rectangle.prototype = Object.create(Shape.prototype);

Czy klasyczne dziedziczenie wykorzystuje wewnątrz dziedziczenie prototypowe?

http://aaditmshah.github.io/why-prototypal-inheritance-matters/

Z powyższego linku dowiedziałem się, że nie możemy dodawać nowych metod w czasie wykonywania w klasycznym dziedziczeniu . Czy to jest poprawne? Ale możesz sprawdzić powyższy kod , mogę dodać metodę "move" i dowolne metody w czasie wykonywania przez prototyp . Więc to jest klasyczne dziedziczenie oparte na prototypach? Jeśli tak, to jakie jest rzeczywiste dziedziczenie klasyczne i prototypowe? Jestem zdezorientowany.

Dziedziczenie prototypowe.

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);

Czy jest to podobne do klasycznego dziedziczenia? Jestem całkowicie zdezorientowany, co to jest dziedziczenie prototypowe? Co to jest klasyczne dziedziczenie? Dlaczego klasyczne dziedziczenie jest złe?

Czy możesz podać prosty przykład, aby lepiej je zrozumieć w prosty sposób.

Dzięki,

Siva

SivaRajini
źródło
Duplikat, sprawdź to: stackoverflow.com/questions/1595611/ ...
Silviu Burcea
5
nie jestem pewien, o co ci chodzi - pierwszy blok kodu to dziedziczenie prototypowe, a nie klasyczne. Twój drugi blok kodu nie ma żadnego dziedziczenia!
Alnitak
To może być xplain
HasanAboShally
@alnitak developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ ... ten link mówi, że jeden był klasycznym dziedziczeniem. dlatego jestem zdezorientowany.
SivaRajini
Aby dowiedzieć się więcej o tym, dlaczego warto unikać klasycznego dziedziczenia, zobacz mój wykład: „Klasyczne dziedziczenie jest przestarzałe: Jak myśleć w prototypowym OO” vimeo.com/69255635
Eric Elliott,

Odpowiedzi:

248

Obie próbki kodu zademonstrowane w Twoim pytaniu wykorzystują dziedziczenie prototypowe. W rzeczywistości każdy kod zorientowany obiektowo, który piszesz w JavaScript, jest paradygmatem dziedziczenia prototypowego. JavaScript po prostu nie ma klasycznego dziedziczenia. To powinno trochę wyjaśnić:

                                   Inheritance
                                        |
                         +-----------------------------+
                         |                             |
                         v                             v
                    Prototypal                     Classical
                         |
         +------------------------------+
         |                              |
         v                              v
Prototypal Pattern             Constructor Pattern

Jak widać dziedziczenie prototypowe i klasyczne to dwa różne paradygmaty dziedziczenia. Niektóre języki, takie jak Self, Lua i JavaScript, obsługują dziedziczenie prototypowe. Jednak większość języków, takich jak C ++, Java i C #, obsługuje klasyczne dziedziczenie.


Krótki przegląd programowania obiektowego

Dziedziczenie prototypowe i klasyczne są paradygmatami programowania obiektowego (tj. Dotyczą obiektów). Obiekty są po prostu abstrakcjami, które obejmują właściwości istoty świata rzeczywistego (tj. Reprezentują rzeczywiste rzeczy słowne w programie). Nazywa się to abstrakcją.

Abstrakcja: Reprezentacja rzeczy ze świata rzeczywistego w programach komputerowych.

Teoretycznie abstrakcję definiuje się jako „ogólną koncepcję utworzoną przez wyodrębnienie wspólnych cech z konkretnych przykładów”. Jednak ze względu na to wyjaśnienie zamiast tego użyjemy powyższej definicji.

Niektóre obiekty mają teraz wiele wspólnego. Na przykład rower błotny i Harley Davidson mają ze sobą wiele wspólnego.

Rower błotny:

Rower błotny.

Harley Davidson:

Harley Davidson

Rower błotny i Harley Davidson to rowery. Stąd rower jest uogólnieniem zarówno roweru błotnego, jak i Harleya Davidsona.

                   Bike
                     |
    +---------------------------------+
    |                                 |
    v                                 v
Mud Bike                       Harley Davidson

W powyższym przykładzie rower, motocykl błotny i Harley Davidson są abstrakcjami. Jednak rower jest bardziej ogólną abstrakcją roweru błotnego i Harleya Davidsona (tj. Zarówno rower błotny, jak i Harley Davidson to specyficzne typy rowerów).

Uogólnienie: abstrakcja bardziej szczegółowej abstrakcji.

W programowaniu obiektowym tworzymy obiekty (które są abstrakcjami bytów ze świata rzeczywistego) i używamy klas lub prototypów do tworzenia uogólnień tych obiektów. Uogólnienia są tworzone przez dziedziczenie. Rower to uogólnienie roweru błotnego. Stąd rowery błotne dziedziczą po rowerach.


Klasyczne programowanie obiektowe

W klasycznym programowaniu obiektowym mamy dwa rodzaje abstrakcji: klasy i obiekty. Obiekt, jak wspomniano wcześniej, jest abstrakcją bytu świata rzeczywistego. Klasa z drugiej strony jest abstrakcją obiektu lub innej klasy (czyli jest uogólnieniem). Weźmy na przykład pod uwagę:

+----------------------+----------------+---------------------------------------+
| Level of Abstraction | Name of Entity |                Comments               |
+----------------------+----------------+---------------------------------------+
| 0                    | John Doe       | Real World Entity.                    |
| 1                    | johnDoe        | Variable holding object.              |
| 2                    | Man            | Class of object johnDoe.              |
| 3                    | Human          | Superclass of class Man.              |
+----------------------+----------------+---------------------------------------+

Jak widać w klasycznych zorientowanych obiektowo językach programowania obiekty są tylko abstrakcjami (tj. Wszystkie obiekty mają poziom abstrakcji 1), a klasy są tylko uogólnieniami (tj. Wszystkie klasy mają poziom abstrakcji większy niż 1).

Obiekty w klasycznych zorientowanych obiektowo językach programowania można tworzyć tylko poprzez tworzenie instancji klas:

class Human {
    // ...
}

class Man extends Human {
    // ...
}

Man johnDoe = new Man();

Podsumowując, w klasycznych zorientowanych obiektowo językach programowania obiekty są abstrakcjami bytów ze świata rzeczywistego, a klasy są uogólnieniami (tj. Abstrakcjami obiektów lub innych klas).

Stąd wraz ze wzrostem poziomu abstrakcji byty stają się bardziej ogólne, a wraz ze spadkiem poziomu abstrakcji, byty stają się bardziej specyficzne. W tym sensie poziom abstrakcji jest analogiczny do skali od bytów bardziej szczegółowych do bytów bardziej ogólnych.


Prototypowe programowanie obiektowe

Prototypowe zorientowane obiektowo języki programowania są znacznie prostsze niż klasyczne zorientowane obiektowo języki programowania, ponieważ w prototypowym programowaniu zorientowanym obiektowo mamy tylko jeden typ abstrakcji (tj. Obiekty). Weźmy na przykład pod uwagę:

+----------------------+----------------+---------------------------------------+
| Level of Abstraction | Name of Entity |                Comments               |
+----------------------+----------------+---------------------------------------+
| 0                    | John Doe       | Real World Entity.                    |
| 1                    | johnDoe        | Variable holding object.              |
| 2                    | man            | Prototype of object johnDoe.          |
| 3                    | human          | Prototype of object man.              |
+----------------------+----------------+---------------------------------------+

Jak widać w prototypowych obiektowych językach programowania, obiekty są abstrakcjami albo bytów świata rzeczywistego (w takim przypadku są po prostu nazywane obiektami), albo innych obiektów (w takim przypadku nazywane są prototypami tych obiektów, które są abstrakcyjne). Stąd prototyp jest uogólnieniem.

Obiekty w prototypowych zorientowanych obiektowo językach programowania mogą być tworzone ex-nihilo (czyli z niczego) lub z innego obiektu (który staje się prototypem nowo utworzonego obiektu):

var human = {};
var man = Object.create(human);
var johnDoe = Object.create(man);

Moim skromnym zdaniem prototypowe zorientowane obiektowo języki programowania są potężniejsze niż klasyczne zorientowane obiektowo języki programowania, ponieważ:

  1. Jest tylko jeden rodzaj abstrakcji.
  2. Uogólnienia to po prostu przedmioty.

Do tej pory musieliście zdać sobie sprawę z różnicy między dziedziczeniem klasycznym a dziedziczeniem prototypowym. Dziedziczenie klasyczne jest ograniczone do klas dziedziczących po innych klasach. Jednak dziedziczenie prototypów obejmuje nie tylko prototypy dziedziczące po innych prototypach, ale także obiekty dziedziczące po prototypach.


Izomorfizm klasy prototypowej

Pewnie zauważyłeś, że prototypy i klasy są bardzo podobne. To prawda. Oni są. W rzeczywistości są tak podobne, że można faktycznie używać prototypów do modelowania klas:

function CLASS(base, body) {
    if (arguments.length < 2) body = base, base = Object.prototype;
    var prototype = Object.create(base, {new: {value: create}});
    return body.call(prototype, base), prototype;

    function create() {
        var self = Object.create(prototype);
        return prototype.hasOwnProperty("constructor") &&
            prototype.constructor.apply(self, arguments), self;
    }
}

Korzystając z powyższej CLASSfunkcji możesz tworzyć prototypy, które wyglądają jak klasy:

var Human = CLASS(function () {
    var milliseconds = 1
      , seconds      = 1000 * milliseconds
      , minutes      = 60 * seconds
      , hours        = 60 * minutes
      , days         = 24 * hours
      , years        = 365.2425 * days;

    this.constructor = function (name, sex, dob) {
        this.name = name;
        this.sex = sex;
        this.dob = dob;
    };

    this.age = function () {
        return Math.floor((new Date - this.dob) / years);
    };
});

var Man = CLASS(Human, function (Human) {
    this.constructor = function (name, dob) {
        Human.constructor.call(this, name, "male", dob);
        if (this.age() < 18) throw new Error(name + " is a boy, not a man!");
    };
});

var johnDoe = Man.new("John Doe", new Date(1970, 0, 1));

Jednak sytuacja odwrotna nie jest prawdą (tj. Nie można używać klas do modelowania prototypów). Dzieje się tak, ponieważ prototypy są obiektami, ale klasy nie są obiektami. To zupełnie inny rodzaj abstrakcji.


Wniosek

Podsumowując, dowiedzieliśmy się, że abstrakcja jest „ogólnym pojęciem utworzonym przez wyodrębnienie wspólnych cech z konkretnych przykładów”, a uogólnienie jest „abstrakcją bardziej szczegółowej abstrakcji” . Dowiedzieliśmy się również o różnicach między dziedziczeniem prototypowym i klasycznym oraz o tym, że oba są dwoma obliczami tej samej monety.

Na pożegnanie chciałbym zauważyć, że istnieją dwa wzorce dziedziczenia prototypowego: wzorzec prototypowy i wzorzec konstruktora. Wzorzec prototypowy to kanoniczny wzorzec dziedziczenia prototypowego, podczas gdy wzorzec konstruktora służy do nadania dziedziczeniu prototypowemu wyglądowi bardziej przypominającego dziedziczenie klasyczne. Osobiście wolę prototypowy wzór.

PS Jestem gościem, który napisał na blogu wpis „ Dlaczego dziedziczenie prototypowe ma znaczenie ” i odpowiedział na pytanie „ Korzyści z dziedziczenia prototypowego nad klasycznym? ”. Moja odpowiedź jest zaakceptowana.

Aadit M Shah
źródło
2
dziękuję za wspaniałą odpowiedź. Muszę zrozumieć, jak prototypowy wzór jest lepszy w porównaniu do wzorca konstruktora. dowolny przykład?
SivaRajini
1
Napisałem krytykę porównawczą na temat konstruktorów vs prototypów na moim blogu: aaditmshah.github.io/why-prototypal-inheritance-matters/…
Aadit M Shah
Czy zatem słuszne byłoby stwierdzenie, że kiedy używamy funkcji w javascript do osiągnięcia dziedziczenia, w pewnym sensie używamy klasycznego modelu dziedziczenia, a gdy używamy zwykłych obiektów, to dziedziczenie prototypowe (oba wewnętrznie po dziedziczeniu prototypowym)?
Swanidhi
1
@Swanidhi Nie. Jeśli używasz JavaScript, to używasz prototypowego modelu dziedziczenia. Jednak JavaScript ma dwa rodzaje dziedziczenia prototypowego: używanie funkcji (tj. Wzorzec konstruktora) i używanie obiektów (tj. Wzorzec prototypowy).
Aadit M Shah
5
@Swanidhi Nie. To wciąż prototyp. JavaScript nie ma „klas”, a więc w klasycznym JavaScript nie ma absolutnie nic, łącznie z konstruktorami. To nadal dziedziczenie prototypowe. Po prostu dziwna forma prototypowego dziedziczenia, którą ludzie mylą z klasycznym dziedziczeniem. W skrócie, programming with classes = classical inheritance, programming with prototypes = prototypal inheritance, programming with constructors = weird form of prototypal inheritance that looks a lot like classical inheritance. Mam nadzieję, że to wszystko wyjaśnia.
Aadit M Shah
8

Zanim przejdziemy do dziedziczenia, przyjrzymy się dwóm podstawowym modelom do tworzenia instancji (obiektów) w javascript:

Model klasyczny: Obiekt jest tworzony z planu (klasa)

class Person {
  fn() {...}
} // or constructor function say, function Person() {}

// create instance
let person = new Person();

Model prototypowy: obiekt jest tworzony bezpośrednio z innego obiektu.

// base object
let Person = { fn(){...} }

// instance
let person = Object.create(Person);

W obu przypadkach dziedziczenie * jest osiągane przez łączenie obiektów za pomocą obiektu prototypowego.

(* metody klasy bazowej są dostępne za pośrednictwem. klasy pochodnej poprzez obiekt prototypu i nie jest wymagane, aby były jawnie obecne w klasie pochodnej).

Oto dobre wyjaśnienie, aby lepiej zrozumieć ( http://www.objectplayground.com/ )

everlasto
źródło
0

Pies to zwierzę. Suzanna to pies. W dziedziczeniu klasycznym Animaljest klasą, Dogjest podklasą Animali suzannajest instancją klasy Dog.

W dziedziczeniu prototypowym nie ma klasy. Masz obiekt animal, który jest obiektem. A dogto kolejny obiekt, który klonuje i rozszerza animal(obiekt prototypowy). suzannato trzeci obiekt, który kopiuje i rozszerza dog.

let animal = {hasChlorophyl: false};

let dog = Object.create(animal);
Object.assign(dog, {
  speak() {
    console.log("Woof!");
  }
});

let suzanna = Object.create(dog);
Object.assign(suzanna, {
  name: "Suzanna"
});

suzanna.speak();

Jeśli Dogzamiast tego napiszesz dog, zwłaszcza jeśli utworzysz Dogjakąś funkcję „konstruktora”, to nie robisz dziedziczenia prototypowego; robisz (pseudo-) klasyczne dziedziczenie . Fakt, że używasz Object.create()do tego celu, nie oznacza, że ​​robisz dziedziczenie prototypowe.

W rzeczywistości JavaScript obsługuje tylko dziedziczenie prototypowe. Mylący newoperator i .prototypeatrybut są po to, aby dziedziczenie prototypowe wyglądało jak dziedziczenie (pseudo) klasyczne.

Douglas Crockford szczegółowo bada to w swojej książce „JavaScript: The Good Parts”.

Antonis Christofides
źródło