eredità classica vs eredità prototipale in javascript


118

Ho cercato su Google così tanti link e non riesco a farmi una buona idea della differenza tra eredità classica ed eredità prototipale?

Ho imparato alcune cose da questi ma sono ancora confuso sui concetti.

Eredità classica

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

L'eredità classica utilizza l'eredità prototipale all'interno?

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

Dal link sopra, ho imparato che non possiamo aggiungere nuovi metodi in fase di esecuzione nell'ereditarietà classica . È corretto? Ma puoi controllare il codice sopra, posso aggiungere il metodo "move" e qualsiasi metodo in fase di esecuzione tramite prototipo . Quindi questa è l'ereditarietà classica basata su prototipi? In caso affermativo, qual è l'eredità classica effettiva e l'eredità prototipo? Sono confuso su questo.

Eredità prototipale.

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

È simile all'eredità classica? Sono totalmente confuso su cosa sia l'eredità prototipale? Cos'è l'eredità classica? Perché l'eredità classica è cattiva?

Puoi farmi un semplice esempio per comprenderli meglio in modo semplice.

Grazie,

Siva



5
Non sono sicuro di cosa stai parlando qui - il primo blocco di codice è un'eredità prototipica, non classica. Il tuo secondo blocco di codice non ha alcuna eredità!
Alnitak


@alnitak developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… questo collegamento indica che uno era un'eredità classica. ecco perché sono confuso.
SivaRajini

Per ulteriori informazioni sul motivo per cui potresti voler evitare l'ereditarietà classica, vedi il mio intervento "Classical Inheritance is Obsolete: How to Think in Prototypal OO" vimeo.com/69255635
Eric Elliott

Risposte:


248

Entrambi gli esempi di codice che hai dimostrato nella tua domanda utilizzano l'ereditarietà prototipale. In effetti, qualsiasi codice orientato agli oggetti che scrivi in ​​JavaScript è un paradigma di ereditarietà prototipale. JavaScript semplicemente non ha l'ereditarietà classica. Questo dovrebbe chiarire un po 'le cose:

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

Come puoi vedere l'ereditarietà prototipale e l'ereditarietà classica sono due diversi paradigmi di eredità. Alcuni linguaggi come Self, Lua e JavaScript supportano l'ereditarietà prototipale. Tuttavia, la maggior parte dei linguaggi come C ++, Java e C # supportano l'ereditarietà classica.


Una rapida panoramica della programmazione orientata agli oggetti

Sia l'ereditarietà prototipale che l'ereditarietà classica sono paradigmi di programmazione orientata agli oggetti (cioè si occupano di oggetti). Gli oggetti sono semplicemente astrazioni che incapsulano le proprietà di un'entità del mondo reale (cioè rappresentano le parole reali nel programma). Questo è noto come astrazione.

Astrazione: la rappresentazione delle cose del mondo reale nei programmi per computer.

Teoricamente un'astrazione è definita come "un concetto generale formato estraendo caratteristiche comuni da esempi specifici". Tuttavia, per il bene di questa spiegazione, utilizzeremo invece la definizione di cui sopra.

Ora alcuni oggetti hanno molte cose in comune. Ad esempio una moto da fango e una Harley Davidson hanno molto in comune.

Una bici da fango:

Una bici da fango.

Una Harley Davidson:

Una Harley Davidson

Una moto da fango e una Harley Davidson sono entrambe moto. Quindi una bici è una generalizzazione sia di una mud bike che di una Harley Davidson.

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

Nell'esempio sopra la bici, la mud bike e la Harley Davidson sono tutte astrazioni. Tuttavia la bici è un'astrazione più generale della mud bike e dell'Harley Davidson (cioè sia la mud bike che la Harley Davidson sono tipi specifici di moto).

Generalizzazione: un'astrazione di un'astrazione più specifica.

Nella programmazione orientata agli oggetti creiamo oggetti (che sono astrazioni di entità del mondo reale) e usiamo classi o prototipi per creare generalizzazioni di questi oggetti. Le generalizzazioni vengono create tramite ereditarietà. Una bici è una generalizzazione di una bici da fango. Quindi le bici da fango ereditano dalle biciclette.


Programmazione classica orientata agli oggetti

Nella classica programmazione orientata agli oggetti abbiamo due tipi di astrazioni: classi e oggetti. Un oggetto, come accennato prima, è un'astrazione di un'entità del mondo reale. Una classe d'altra parte è un'astrazione di un oggetto o di un'altra classe (cioè è una generalizzazione). Ad esempio, considera:

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

Come puoi vedere nei linguaggi di programmazione orientati agli oggetti classici, gli oggetti sono solo astrazioni (cioè tutti gli oggetti hanno un livello di astrazione di 1) e le classi sono solo generalizzazioni (cioè tutte le classi hanno un livello di astrazione maggiore di 1).

Gli oggetti nei classici linguaggi di programmazione orientati agli oggetti possono essere creati solo istanziando le classi:

class Human {
    // ...
}

class Man extends Human {
    // ...
}

Man johnDoe = new Man();

In sintesi, nei linguaggi di programmazione classici orientati agli oggetti gli oggetti sono astrazioni di entità del mondo reale e le classi sono generalizzazioni (cioè astrazioni di oggetti o di altre classi).

Quindi, quando il livello di astrazione aumenta, le entità diventano più generali e quando il livello di astrazione diminuisce, le entità diventano più specifiche. In questo senso il livello di astrazione è analogo a una scala che va da entità più specifiche a entità più generali.


Programmazione prototipale orientata agli oggetti

I linguaggi di programmazione prototipali orientati agli oggetti sono molto più semplici dei classici linguaggi di programmazione orientati agli oggetti perché nella programmazione prototipale orientata agli oggetti abbiamo solo un tipo di astrazione (cioè gli oggetti). Ad esempio, considera:

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

Come puoi vedere nei linguaggi di programmazione orientati agli oggetti prototipali, gli oggetti sono astrazioni di entità del mondo reale (nel qual caso sono semplicemente chiamati oggetti) o di altri oggetti (nel qual caso sono chiamati prototipi di quegli oggetti che astraggono). Quindi un prototipo è una generalizzazione.

Gli oggetti in linguaggi di programmazione orientati agli oggetti prototipali possono essere creati ex-nihilo (cioè dal nulla) o da un altro oggetto (che diventa il prototipo dell'oggetto appena creato):

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

A mio modesto parere, i linguaggi di programmazione orientati agli oggetti prototipi sono più potenti dei linguaggi di programmazione orientati agli oggetti classici perché:

  1. C'è solo un tipo di astrazione.
  2. Le generalizzazioni sono semplicemente oggetti.

A questo punto devi aver capito la differenza tra eredità classica ed eredità prototipale. L'ereditarietà classica è limitata alle classi che ereditano da altre classi. Tuttavia, l'ereditarietà dei prototipi include non solo i prototipi che ereditano da altri prototipi, ma anche gli oggetti che ereditano dai prototipi.


Isomorfismo di classe prototipo

Avrai notato che i prototipi e le classi sono molto simili. È vero. Loro sono. In effetti sono così simili che puoi effettivamente utilizzare i prototipi per modellare le classi:

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

Usando la CLASSfunzione sopra puoi creare prototipi che assomigliano a classi:

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

Tuttavia non è vero il contrario (cioè non puoi usare le classi per modellare prototipi). Questo perché i prototipi sono oggetti ma le classi non sono oggetti. Sono un tipo di astrazione completamente diverso.


Conclusione

Riassumendo, abbiamo appreso che un'astrazione è un "concetto generale formato estraendo caratteristiche comuni da esempi specifici" e che la generalizzazione è "un'astrazione di un'astrazione più specifica" . Abbiamo anche appreso le differenze tra eredità prototipale e classica e come entrambe siano due facce della stessa moneta.

In una nota di separazione vorrei sottolineare che ci sono due modelli di ereditarietà prototipale: il modello prototipale e il modello costruttore. Il modello prototipale è il modello canonico dell'ereditarietà prototipale mentre il modello del costruttore viene utilizzato per rendere l'ereditarietà prototipale più simile all'eredità classica. Personalmente preferisco il modello prototipale.

PS Sono il ragazzo che ha scritto il post sul blog " Why Prototypal Inheritance Matters " e ha risposto alla domanda " Benefits of prototypal inheritance over classic? ". La mia risposta è la risposta accettata.


2
grazie per la tua meravigliosa risposta. ho bisogno di capire come il modello prototipale è meglio confrontare con il modello del costruttore. qualsiasi esempio?
SivaRajini

1
Ho scritto una critica comparativa sui costruttori e sui prototipi nel mio blog: aaditmshah.github.io/why-prototypal-inheritance-matters/…
Aadit M Shah

Quindi, sarebbe corretto dire che quando usiamo funzioni in javascript per ottenere l'ereditarietà, usiamo in qualche modo il modello di ereditarietà classica e quando usiamo oggetti semplici si tratta di eredità prototipica (entrambi internamente seguono l'ereditarietà prototipica)?
Swanidhi

1
@Swanidhi No. Se stai usando JavaScript, stai usando il modello di ereditarietà prototipale. Tuttavia, JavaScript ha due tipi di ereditarietà prototipale: usare le funzioni (cioè il modello del costruttore) e usare gli oggetti (cioè il modello prototipale).
Aadit M Shah

5
@Swanidhi No. È ancora un prototipo. JavaScript non ha "classi" e quindi assolutamente nulla in JavaScript classico, inclusi i costruttori. È ancora un'eredità prototipale. Solo una strana forma di eredità prototipale che le persone confondono con l'eredità classica. In breve, 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. Spero che questo chiarisca le cose.
Aadit M Shah

8

Prima di passare all'ereditarietà, daremo un'occhiata a due modelli primari per creare istanze (oggetti) in javascript:

Modello classico: l' oggetto viene creato da un progetto (classe)

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

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

Modello prototipale: l' oggetto viene creato direttamente da un altro oggetto.

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

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

In entrambi i casi, l'ereditarietà * si ottiene collegando gli oggetti utilizzando l'oggetto prototipo.

(* i metodi della classe base sono accessibili tramite la classe derivata tramite l'oggetto prototipo e non è necessario che siano presenti esplicitamente nella classe derivata.)

Ecco una buona spiegazione per capire meglio ( http://www.objectplayground.com/ )


0

Un cane è un animale. Suzanna è un cane. Nell'ereditarietà classica, Animalè una classe, Dogè una sottoclasse di Animaled suzannaè un'istanza di a Dog.

Nell'ereditarietà prototipale, non esiste una classe. Hai un animal, che è un oggetto. A dogè un altro oggetto, che clona ed estende animal(l'oggetto prototipo). suzannaè un terzo oggetto, che copia ed estende 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();

Se scrivi Doginvece di dog, specialmente se crei Doguna sorta di funzione "costruttore", non stai facendo l'ereditarietà prototipale; stai facendo un'eredità (pseudo-) classica . Il fatto che tu stia usando Object.create()per raggiungere questo obiettivo non significa che stai facendo un'eredità prototipale.

Infatti, JavaScript supporta solo l'ereditarietà prototipale. L' newoperatore e l' .prototypeattributo che creano confusione servono per far sembrare l'ereditarietà prototipale come l'ereditarietà (pseudo-) classica.

Douglas Crockford lo esplora a lungo nel suo libro "JavaScript: The Good Parts".

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.