2011-01-19 3 views
73

Mi piacerebbe capire quando è opportuno utilizzare i metodi prototipo in js. Dovrebbero essere sempre usati? O ci sono casi in cui il loro utilizzo non è preferibile e/o incorre in una penalizzazione delle prestazioni?Javascript quando utilizzare i prototipi

Nella ricerca intorno a questo sito su metodi comuni per namespaces in js, sembra che la maggior parte usi un'implementazione basata su un prototipo: semplicemente usando un oggetto o un oggetto funzione per incapsulare uno spazio dei nomi.

Provenendo da un linguaggio basato sulla classe, è difficile non provare a tracciare paralleli e pensare che i prototipi siano come "classi" e le implementazioni dei namespace che ho menzionato sono come metodi statici.

risposta

115

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.

+1

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

+0

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

+3

** 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 –

11

Metti le funzioni su un oggetto prototipo quando creerai molte copie di un particolare tipo di oggetto e tutti dovranno condividere comportamenti comuni. In tal modo, potrai risparmiare un po 'di memoria avendo una sola copia di ogni funzione, ma questo è solo il vantaggio più semplice.

La modifica dei metodi sugli oggetti prototipo o l'aggiunta di metodi modifica istantaneamente la natura di tutte le istanze dei tipi corrispondenti.

Ora esattamente perché lo si dovrebbe fare tutte queste cose è principalmente una funzione della propria progettazione dell'applicazione e il tipo di cose che è necessario fare nel codice lato client. (Un'intera storia diversa sarebbe il codice all'interno di un server, molto più facile immaginare di fare più codice "OO" su larga scala.)

+0

così quando istanzio un nuovo oggetto con i metodi prototipo (tramite nuova parola chiave), allora quell'oggetto non ottiene una nuova copia di ogni funzione (solo una specie di puntatore)? Se questo è il caso, perché non vorresti usare un prototipo? – opl

+0

come @marcel, d'oh ... =) – hellatan

+0

@opi sì, hai ragione - non c'è nessuna copia fatta. Invece, i simboli (nomi di proprietà) sull'oggetto prototipo sono semplicemente "là" naturalmente come parti virtuali di ciascun oggetto istanza. L'unica ragione per cui le persone non vorrebbero preoccuparsi di ciò sarebbe casi in cui gli oggetti sono di breve durata e distinti, o dove non c'è molto "comportamento" da condividere. – Pointy

1

Se spiego in termini di classe, Person è classe, walk() è metodo Prototype . Quindi walk() avrà la sua esistenza solo dopo aver istanziato un nuovo oggetto con questo.

Quindi se si desidera creare le copie dell'oggetto come Persona è possibile creare molti utenti Prototype è una buona soluzione in quanto salva la memoria condividendo/ereditando la stessa copia di funzione per ciascun oggetto in memoria.

Considerando che lo statico non è di grande aiuto in tale scenario.

function Person(){ 
this.name = "anonymous"; 
} 

// its instance method and can access objects data data 
Person.prototype.walk = function(){ 
alert("person has started walking."); 
} 
// its like static method 
Person.ProcessPerson = function(Person p){ 
alert("Persons name is = " + p.name); 
} 

var userOne = new Person(); 
var userTwo = new Person(); 

//Call instance methods 
userOne.walk(); 

//Call static methods 
Person.ProcessPerson(userTwo); 

Quindi con questo è più simile al metodo di istanza. L'approccio dell'oggetto è come i metodi statici.

https://developer.mozilla.org/en/Introduction_to_Object-Oriented_JavaScript

40

È necessario utilizzare i prototipi se si desidera dichiarare un metodo "non statico" dell'oggetto.

var myObject = function() { 

}; 

myObject.prototype.getA = function(){ 
    alert("A"); 
}; 

myObject.getB = function(){ 
    alert("B"); 
}; 

myObject.getB(); // This works fine 

myObject.getA(); // Error! 

var myPrototypeCopy = new myObject(); 
myPrototypeCopy.getA(); // This works, too. 
+4

+1 Bel punto. Non l'avevo considerato. – GFoley83

+0

@keatsKelleher ma possiamo creare un metodo non statico per l'oggetto semplicemente definendo il metodo all'interno della funzione di costruzione usando 'this' example' this.getA = function() {alert ("A")} 'giusto? –

13

Uno dei motivi per utilizzare il built-in prototype oggetto è se sarete duplicare un oggetto più volte che condivideranno funzionalità comuni. Associando i metodi al prototipo, è possibile salvare i metodi di duplicazione creati per ogni istanza new.Ma quando si allega un metodo allo prototype, tutte le istanze avranno accesso a tali metodi.

Supponiamo di disporre di una classe/oggetto Car() di base.

function Car() { 
    // do some car stuff 
} 

quindi di creare più istanze Car().

var volvo = new Car(), 
    saab = new Car(); 

Ora, si sa ogni vettura dovrà guidare, accendere, ecc, invece di collegare direttamente un metodo alla classe Car() (che occupa memoria per ogni istanza creato), è possibile collegare i metodi per il prototipo invece (creando i metodi una sola volta), dando quindi accesso a questi metodi sia al nuovo volvo che a saab.

// just mapping for less typing 
Car.fn = Car.prototype; 

Car.fn.drive = function() { 
    console.log("they see me rollin'"); 
}; 
Car.fn.honk = function() { 
    console.log("HONK!!!"); 
} 

volvo.honk(); 
// => HONK!!! 
saab.drive(); 
// => they see me rollin' 
+2

in realtà non è corretto. volvo.honk() non funzionerà perché hai completamente sostituito l'oggetto prototipo, non lo hai esteso. Se dovessi fare qualcosa di simile, funzionerebbe come ti aspetti: Car.prototype.honk = function() {console.log ('HONK');} volvo.honk(); // 'HONK' – 29er

+1

@ 29er - nel modo in cui ho scritto questo esempio, sei corretto. L'ordine conta. Se dovessi mantenere questo esempio così com'è, il 'Car.prototype = {...}' dovrebbe venire prima di chiamare una 'nuova Car()' come illustrato in questo jsfiddle: http://jsfiddle.net/mxacA /. Per quanto riguarda il tuo argomento, questo sarebbe il modo corretto per farlo: http://jsfiddle.net/Embnp/. La cosa divertente è che non ricordo di aver risposto a questa domanda =) – hellatan

+0

@hellatan puoi risolvere il problema impostando il costruttore: Car a poiché hai sovrascritto la proprietà prototype con un oggetto letterale. –