Benefici dell’ereditarietà prototipale rispetto al classico?

Così alla fine ho smesso di trascinarmi in tutti questi anni e ho deciso di imparare JavaScript “correttamente”. Uno degli elementi più problematici del design linguistico è l’implementazione dell’ereditarietà. Avendo esperienza in Ruby, ero davvero felice di vedere chiusure e digitazione dynamic; ma per la vita di me non riesco a capire quali benefici si debbano ottenere dalle istanze di oggetti che usano altre istanze per ereditarietà.

So che questa risposta è di 3 anni in ritardo, ma penso davvero che le risposte attuali non forniscano sufficienti informazioni su come l’eredità prototipale sia migliore dell’ereditarietà classica .

Per prima cosa vediamo gli argomenti più comuni che i programmatori JavaScript dichiarano in difesa dell’ereditarietà prototipale (sto prendendo questi argomenti dal corrente pool di risposte):

  1. È semplice.
  2. È potente
  3. Porta a un codice più piccolo, meno ridondante.
  4. È dinamico e quindi è meglio per le lingue dinamiche.

Ora questi argomenti sono tutti validi, ma nessuno si è preoccupato di spiegare il perché. È come dire a un bambino che studiare la matematica è importante. Certo che lo è, ma al bambino di certo non importa; e non puoi fare un bambino come la matematica dicendo che è importante.

Penso che il problema con l’ereditarietà prototipale sia che è spiegato dal punto di vista di JavaScript. Adoro JavaScript, ma l’ereditarietà del prototipo in JavaScript è sbagliata. A differenza dell’ereditarietà classica, esistono due modelli di eredità prototipale:

  1. Il modello prototipale dell’eredità prototipale.
  2. Il modello di costruzione dell’ereditarietà prototipale.

Sfortunatamente JavaScript utilizza il modello di costruzione dell’ereditarietà prototipale. Questo perché quando JavaScript è stato creato, Brendan Eich (il creatore di JS) voleva che assomigliasse a Java (che ha ereditarietà classica):

E lo stavamo spingendo come un fratello minore a Java, in quanto un linguaggio complementare come Visual Basic era in C ++ nelle famiglie linguistiche di Microsoft al momento.

Questo è male perché quando le persone usano i costruttori in JavaScript pensano ai costruttori che ereditano da altri costruttori. Questo è sbagliato. In oggetti di ereditarietà prototipali ereditati da altri oggetti. I costruttori non entrano mai in scena. Questo è ciò che confonde molte persone.

Le persone di lingue come Java, che hanno ereditarietà classica, si fanno ancora più confuse perché sebbene i costruttori assomiglino a classi che non si comportano come classi. Come ha affermato Douglas Crockford :

Questa indiretta era intesa a far sembrare la lingua più familiare ai programmatori addestrati in modo classico, ma non ci riuscì, come possiamo vedere dall’opinione molto bassa che i programmatori Java hanno di JavaScript. Il modello di costruzione di JavaScript non ha attirato la folla classica. Ha inoltre oscurato la vera natura prototipale di JavaScript. Di conseguenza, ci sono pochissimi programmatori che sanno usare la lingua in modo efficace.

Ecco qua. Direttamente dalla bocca del cavallo.

Vera eredità prototipale

L’ereditarietà prototipale riguarda esclusivamente gli oggetti. Gli oggetti ereditano le proprietà da altri oggetti. Questo è tutto ciò che c’è da fare. Esistono due modi per creare oggetti utilizzando l’ereditarietà prototipale:

  1. Crea un object nuovo di zecca.
  2. Clona un object esistente ed estendi.

Nota: JavaScript offre due modi per clonare un object: delega e concatenazione . D’ora in poi userò la parola “clone” per riferirsi esclusivamente all’ereditarietà tramite delega e la parola “copia” per riferirsi esclusivamente all’ereditarietà tramite concatenazione.

Basta parlare. Vediamo alcuni esempi. Diciamo che ho un cerchio di raggio 5 :

 var circle = { radius: 5 }; 

Possiamo calcolare l’area e la circonferenza del cerchio dal suo raggio:

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

Ora voglio creare un’altra cerchia di raggio 10 . Un modo per farlo sarebbe:

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

Tuttavia JavaScript offre un modo migliore: la delega . La funzione Object.create di Crockford è usata per fare questo:

 var circle2 = Object.create(circle); circle2.radius = 10; 

È tutto. Hai appena realizzato un’eredità prototipale in JavaScript. Non era così semplice? Prendi un object, clonalo, cambia tutto ciò di cui hai bisogno, e presto – ti sei fatto un object nuovo di zecca.

Ora potresti chiedere: “Com’è semplice? Ogni volta che voglio creare un nuovo cerchio ho bisogno di clonare il circle e assegnarlo manualmente un raggio”. Bene, la soluzione è usare una funzione per fare il sollevamento pesante per te:

 function createCircle(radius) { var newCircle = Object.create(circle); newCircle.radius = radius; return newCircle; } var circle2 = createCircle(10); 

In effetti puoi combinare tutto questo in un singolo object letterale come segue:

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

Ereditarietà prototipale in JavaScript

Se noti nel programma precedente la funzione crea crea un clone di circle , assegna un nuovo radius ad esso e poi lo restituisce. Questo è esattamente ciò che fa un costruttore in 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); 

Il modello di costruzione in JavaScript è il modello prototipo invertito. Invece di creare un object, crei un costruttore. La new parola chiave associa il puntatore all’interno del costruttore a un clone del prototype del costruttore.

Sembra confuso? È perché il modello di costruzione in JavaScript complica inutilmente le cose. Questo è ciò che la maggior parte dei programmatori trova difficile da capire.

Invece di pensare a oggetti che ereditano da altri oggetti, pensano a costruttori che ereditano da altri costruttori e poi diventano completamente confusi.

Ci sono un sacco di altri motivi per cui il modello di costruzione in JavaScript dovrebbe essere evitato. Puoi leggere su di loro nel mio post del blog qui: Costruttori vs Prototipi


Quindi quali sono i vantaggi dell’ereditarietà prototipale rispetto all’ereditarietà classica? Riprendiamo gli argomenti più comuni e spieghiamo perché .

1. L’ereditarietà del prototipo è semplice

CMS afferma nella sua risposta:

A mio avviso, il principale vantaggio dell’ereditarietà prototipale è la sua semplicità.

Consideriamo ciò che abbiamo appena fatto. Abbiamo creato un circle oggetti con un raggio di 5 . Poi l’abbiamo clonato e abbiamo dato al clone un raggio di 10 .

Quindi abbiamo solo bisogno di due cose per fare dell’ereditarietà prototipale:

  1. Un modo per creare un nuovo object (es. Letterali dell’object).
  2. Un modo per estendere un object esistente (ad es. Object.create ).

Al contrario l’ereditarietà classica è molto più complicata. Nell’eredità classica hai:

  1. Classi.
  2. Oggetto.
  3. Interfacce.
  4. Classi astratte.
  5. Classi finali.
  6. Classi di base virtuale.
  7. Costruttori.
  8. Distruttori.

Hai un’idea. Il punto è che l’eredità prototipale è più facile da capire, più facile da implementare e più facile da ragionare.

Come dice Steve Yegge nel suo blog post ” Portrait of a N00b “:

I metadati sono qualsiasi tipo di descrizione o modello di qualcos’altro. I commenti nel tuo codice sono solo una descrizione in linguaggio naturale del calcolo. Ciò che rende metadati i meta-dati è che non è strettamente necessario. Se ho un cane con un po ‘di documenti di pedigree, e perdo le carte, ho ancora un cane perfettamente valido.

Nello stesso senso le classi sono solo meta-dati. Le classi non sono strettamente richieste per l’ereditarietà. Tuttavia alcune persone (di solito n00bs) trovano le classi più comode con cui lavorare. Dà loro un falso senso di sicurezza.

Bene, sappiamo anche che i tipi statici sono solo metadati. Sono un tipo specializzato di commenti rivolti a due tipi di lettori: programmatori e compilatori. I tipi statici raccontano una storia sul calcolo, presumibilmente per aiutare entrambi i gruppi di lettori a comprendere l’intento del programma. Ma i tipi statici possono essere eliminati in fase di esecuzione, perché alla fine sono solo commenti stilizzati. Sono come documenti di genealogia: potrebbe rendere un certo tipo di personalità insicuro più felice del loro cane, ma il cane di certo non gli importa.

Come ho affermato prima, le classi danno alla gente un falso senso di sicurezza. Ad esempio si ottengono troppi NullPointerException in Java anche quando il codice è perfettamente leggibile. Trovo che l’ereditarietà classica di solito interferisca con la programmazione, ma forse è solo Java. Python ha un incredibile sistema di ereditarietà classica.

2. L’ereditarietà prototipale è potente

La maggior parte dei programmatori che provengono da un contesto classico sostengono che l’ereditarietà classica è più potente dell’eredità prototipale perché ha:

  1. Variabili private
  2. Eredità multipla

Questa affermazione è falsa. Sappiamo già che JavaScript supporta variabili private tramite chiusure , ma per quanto riguarda l’ereditarietà multipla? Gli oggetti in JavaScript hanno solo un prototipo.

La verità è che l’ereditarietà prototipale supporta l’ereditarietà di più prototipi. L’ereditarietà prototipale significa semplicemente un object che eredita da un altro object. Esistono due modi per implementare l’ereditarietà del prototipo :

  1. Delega o eredità differenziale
  2. Clonazione o eredità concatenativa

Sì, JavaScript consente solo agli oggetti di debind a un altro object. Tuttavia, consente di copiare le proprietà di un numero arbitrario di oggetti. Ad esempio, _.extend fa proprio questo.

Ovviamente molti programmatori non considerano questa come vera ereditarietà perché instanceof ed isPrototypeOf dicono diversamente. Tuttavia, questo può essere facilmente risolto memorizzando una serie di prototipi su ogni object che eredita da un prototipo tramite concatenazione:

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

Quindi l’eredità prototipale è potente quanto l’ereditarietà classica. In effetti è molto più potente dell’ereditarietà classica perché nell’ereditarietà prototipale è ansible scegliere a mano quali proprietà copiare e quali proprietà omettere da diversi prototipi.

Nell’ereditarietà classica è imansible (o almeno molto difficile) scegliere quali proprietà si desidera ereditare. Usano le classi di base virtuali e le interfacce per risolvere il problema dei diamanti .

In JavaScript, tuttavia, molto probabilmente non si sentirà mai parlare del problema dei diamanti perché è ansible controllare esattamente quali proprietà si desidera ereditare e da quali prototipi.

3. L’ereditarietà prototipale è meno ridondante

Questo punto è un po ‘più difficile da spiegare perché l’ereditarietà classica non porta necessariamente a un codice ridondante. In effetti l’ereditarietà, classica o prototipale, viene utilizzata per ridurre la ridondanza nel codice.

Un argomento potrebbe essere che la maggior parte dei linguaggi di programmazione con ereditarietà classica sono tipizzati staticamente e richiedono all’utente di dichiarare esplicitamente i tipi (diversamente da Haskell che ha una tipizzazione implicita statica). Quindi questo porta a un codice più dettagliato.

Java è famoso per questo comportamento. Ricordo distintamente Bob Nystrom menzionando il seguente aneddoto nel suo post sul blog su Pratt Parsers :

Devi amare il livello di burocrazia “per favore firmalo in quadruplice” di Java qui.

Ancora una volta, penso che sia solo perché Java fa schifo così tanto.

Un argomento valido è che non tutte le lingue che hanno ereditarietà classica supportano l’ereditarietà multipla. Ancora una volta mi viene in mente Java. Sì, Java ha interfacce, ma non è sufficiente. A volte hai davvero bisogno di ereditarietà multipla.

Poiché l’ereditarietà prototipale consente l’ereditarietà multipla, il codice che richiede un’ereditarietà multipla è meno ridondante se viene scritto utilizzando l’ereditarietà prototipale piuttosto che in un linguaggio che ha ereditarietà classica ma nessuna eredità multipla.

4. L’ereditarietà prototipale è dynamic

Uno dei vantaggi più importanti dell’ereditarietà prototipale è che è ansible aggiungere nuove proprietà ai prototipi dopo la loro creazione. Ciò consente di aggiungere nuovi metodi a un prototipo che verrà automaticamente reso disponibile a tutti gli oggetti che delegano a tale prototipo.

Questo non è ansible nell’ereditarietà classica perché una volta creata una class non è ansible modificarla in fase di runtime. Questo è probabilmente il più grande vantaggio dell’ereditarietà prototipale rispetto all’ereditarietà classica e dovrebbe essere al primo posto. Comunque mi piace salvare il meglio per la fine.

Conclusione

L’ereditarietà del prototipo è importante. È importante educare i programmatori JavaScript sul perché abbandonare il modello di costruzione dell’ereditarietà prototipale in favore del modello prototipale dell’ereditarietà prototipale.

Abbiamo bisogno di iniziare a insegnare JavaScript correttamente e questo significa mostrare ai nuovi programmatori come scrivere codice usando il modello prototipale invece del modello del costruttore.

Non solo sarà più facile spiegare l’ereditarietà del prototipo usando il modello prototipale, ma renderà anche i programmatori migliori.

Se questa risposta ti è piaciuta, dovresti anche leggere il mio post sul blog ” Perché l’inerzia prototipale è importante “. Fidati di me, non rimarrai deluso.

Consentitemi di rispondere effettivamente alla domanda in linea.

L’ereditarietà del prototipo ha le seguenti virtù:

  1. È più adatto ai linguaggi dinamici perché l’ereditarietà è dynamic quanto l’ambiente in cui si trova. (L’applicabilità a JavaScript dovrebbe essere ovvia qui.) Ciò ti permette di fare le cose rapidamente al volo come personalizzare le classi senza enormi quantità di codice di infrastruttura .
  2. È più semplice implementare uno schema di oggetti di prototipazione rispetto agli schemi classici di dicotomia class / object.
  3. Elimina la necessità degli spigoli complessi attorno al modello object come “metaclasss” (non ho mai incontrato il metaclass che mi è piaciuto … scusate!) O “autovalori” o simili.

Tuttavia presenta i seguenti svantaggi:

  1. Digitare il controllo di un linguaggio prototipo non è imansible, ma è molto, molto difficile. La maggior parte del “controllo del tipo” dei linguaggi prototipici è puro controllo “digitazione anatra” in fase di esecuzione. Questo non è adatto a tutti gli ambienti.
  2. È ugualmente difficile fare cose come ottimizzare l’invio del metodo mediante analisi statiche (o, spesso, anche dinamiche!). Può (sottolineo: può ) essere molto inefficiente molto facilmente.
  3. Allo stesso modo la creazione di oggetti può essere (e di solito è) molto più lenta in un linguaggio di prototipazione di quanto non lo sia in uno schema di dicotomia di class / object più convenzionale.

Penso che tu possa leggere tra le righe sopra e trovare i vantaggi e gli svantaggi dei tradizionali schemi di class / object. Ci sono, ovviamente, di più in ogni area, quindi lascerò il resto alle altre persone che rispondono.

IMO il principale vantaggio dell’ereditarietà prototipale è la sua semplicità.

La natura prototipale della lingua può confondere persone che sono classicamente addestrate, ma si scopre che in realtà questo è un concetto molto semplice e potente, l’ eredità differenziale .

Non è necessario effettuare la classificazione , il codice è più piccolo, meno ridondante, gli oggetti ereditano da altri oggetti più generali.

Se pensi prototipicamente noterai presto che non hai bisogno di lezioni …

L’ereditarietà prototipale sarà molto più popolare nel prossimo futuro, la specifica ECMAScript 5th Edition ha introdotto il metodo Object.create , che consente di produrre una nuova istanza di object che eredita da un’altra in un modo molto semplice:

 var obj = Object.create(baseInstance); 

Questa nuova versione dello standard è in fase di implementazione da parte di tutti i produttori di browser, e penso che inizieremo a vedere più pura eredità prototipale …

Non c’è davvero molto da scegliere tra i due metodi. L’idea di base è che quando il motore JavaScript riceve una proprietà di un object da leggere, prima controlla l’istanza e se quella proprietà è mancante, controlla la catena del prototipo. Ecco un esempio che mostra la differenza tra prototipo e classica:

prototipale

 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) 

Classico con metodi di istanza (Inefficiente perché ogni istanza memorizza la propria proprietà)

 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 

Classico efficiente

 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" 

Come potete vedere, poiché è ansible manipolare il prototipo di “classi” dichiarate in stile classico, non vi è alcun vantaggio nell’utilizzo dell’ereditarietà prototipale. È un sottoinsieme del metodo classico.

Sviluppo Web: Prototipazione dell’ereditarietà rispetto all’eredità classica

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

Eredità prototipale classica vs Stack Overflow

Ereditarietà prototipale classica vs

In Javascript, con modal prototipale, non puoi eseguire instanceOf. Puoi con il modello classico. Ho intenzione di mettere un link qui per te. http://www.objectplayground.com Il miglior video che parla del prototipo di Javascript.

Con l’arrivo di ECMA 6, Javascript supporterà la nuova syntax di class. Quindi VERO modello classico sta arrivando.

Il futuro si attiene al modello classico.