prototipi sono un ottimizzazione.
Un ottimo esempio di come utilizzarli è la libreria jQuery. Ogni volta che ottieni un oggetto jQuery usando $('.someClass')
, quell'oggetto ha dozzine di "metodi". La biblioteca potrebbe raggiungere questo restituendo un oggetto:
return {
show: function() { ... },
hide: function() { ... },
css: function() { ... },
animate: function() { ... },
// etc...
};
Ma questo vorrebbe dire che ogni oggetto jQuery in memoria avrebbe decine di slot designati contenenti gli stessi metodi, più e più volte.
Invece, questi metodi sono definiti su un prototipo e tutti gli oggetti jQuery "ereditano" tale prototipo in modo da ottenere tutti quei metodi a costi di runtime molto bassi.
Una parte di vitale importanza di come jQuery ottiene correttamente è che questo è nascosto dal programmatore. È considerato puramente un'ottimizzazione, non come qualcosa di cui ti devi preoccupare quando usi la libreria.
Il problema con JavaScript è che le funzioni di costruzione nude richiedono che il chiamante si ricordi di anteporre il prefisso a new
o che in genere non funzionano. Non c'è una buona ragione per questo. jQuery lo fa bene nascondendo quella assurdità dietro una funzione ordinaria, $
, quindi non devi preoccuparti di come gli oggetti sono implementati.
Per poter creare comodamente un oggetto con un prototipo specificato, ECMAScript 5 include una funzione standard Object.create
. Una versione molto semplificata di esso sarebbe simile a questa:
Object.create = function(prototype) {
var Type = function() {};
Type.prototype = prototype;
return new Type();
};
ci vuole solo la cura del dolore di scrivere una funzione di costruzione e quindi chiamando con new
.
Quando si desidera evitare i prototipi?
Un utile confronto è con i linguaggi OO popolari come Java e C#. Questi supportano due tipi di ereditarietà:
- interfaccia eredità, dove si
implement
un interface
tale che la classe fornisce la propria implementazione unica per ogni membro dell'interfaccia.
- implementazione ereditarietà, dove è
extend
a class
che fornisce le implementazioni predefinite di alcuni metodi.
In JavaScript, l'ereditarietà prototipo è una sorta di implementazione eredità. Quindi in quelle situazioni in cui (in C# o in Java) si sarebbe derivato da una classe base per ottenere un comportamento predefinito, che quindi si apportano piccole modifiche tramite le sostituzioni, quindi in JavaScript l'ereditarietà prototipale ha senso.
Tuttavia, se ti trovi in una situazione in cui avresti usato le interfacce in C# o Java, non hai bisogno di alcuna particolare lingua in JavaScript. Non c'è bisogno di dichiarare esplicitamente qualcosa che rappresenta l'interfaccia, e non c'è bisogno di contrassegnare gli oggetti come "di esecuzione" che si interfacciano:
var duck = {
quack: function() { ... }
};
duck.quack(); // we're satisfied it's a duck!
In altre parole, se ogni "tipo" di oggetto ha le proprie definizioni del "metodi", quindi non vi è alcun valore in ereditare da un prototipo. Dopodiché, dipende da quante istanze allocate di ciascun tipo. Ma in molti progetti modulari, esiste solo un'istanza di un determinato tipo.
E infatti, it has been suggested by many people that implementation inheritance is evil. Cioè, se ci sono alcune operazioni comuni per un tipo, allora forse è più chiaro se non sono messe in una classe base/super, ma sono invece esposte semplicemente come funzioni ordinarie in qualche modulo, a cui si passa l'oggetto (i) vuoi che funzionino.
Buona spiegazione. Allora, sei d'accordo sul fatto che, dal momento che consideri i prototipi come un'ottimizzazione, possono sempre essere utilizzati per migliorare il tuo codice? Mi chiedo se ci siano casi in cui l'utilizzo di prototipi non ha senso, o in realtà incorre in una penalizzazione delle prestazioni. – opl
Nel tuo follow up, dici che "dipende da quante istanze allocate di ogni tipo". Ma l'esempio a cui fai riferimento non utilizza i prototipi. Dov'è la nozione di allocare un'istanza (staresti ancora usando "nuovo" qui)? Inoltre: diciamo che il metodo del ciarlatano aveva un parametro - ogni invocazione di duck.quack (param) causerebbe la creazione di un nuovo oggetto in memoria (forse è irrilevante se ha un parametro o meno)? – opl
** 1. ** Volevo dire che se ci fosse un gran numero di istanze di un tipo di anatra, allora avrebbe senso modificare l'esempio in modo che la funzione 'ciarlatano 'fosse in un prototipo, a cui i molti anatra le istanze sono collegate. ** 2. ** La sintassi letterale dell'oggetto '{...}' crea un'istanza (non è necessario usare 'new' con essa). ** 3. ** Chiamando qualsiasi funzione JS fa in modo che almeno un oggetto venga creato in memoria - è chiamato l'oggetto 'arguments' e memorizza gli argomenti passati nella chiamata: https://developer.mozilla.org/en/JavaScript/Reference/functions_and_function_scope/arguments –