Uso di "prototipo" vs. "questo" in JavaScript?


776

Qual è la differenza tra

var A = function () {
    this.x = function () {
        //do something
    };
};

e

var A = function () { };
A.prototype.x = function () {
    //do something
};


il concetto di QUESTA parola chiave è spiegato esplicitamente qui scotch.io/@alZami/understanding-this-in-javascript
AL-zami

1
Leggere "questo" thread mostra quanto sia orribile JS e quanto i suoi principi non siano chiari per molti sviluppatori. Cosa c'è di esattamente sbagliato nelle lingue più facili da capire? Penso che sia tempo che gli sviluppatori alzino la voce per rifiutare tecnologie confuse che non servono a nulla o poco per il lavoro aziendale o di sviluppo.
NoChance,

Su oggetto: a1.x !== a2.x; sul prototipo:a1.x === a2.x
Juan Mendes,

Risposte:


467

Gli esempi hanno esiti molto diversi.

Prima di esaminare le differenze, è necessario tenere presente quanto segue:

  • Il prototipo di un costruttore fornisce un modo per condividere metodi e valori tra istanze tramite la [[Prototype]]proprietà privata dell'istanza .
  • Una funzione di questo viene impostata da come la funzione viene chiamata o con l'uso di bind (non discusso qui). Quando una funzione viene chiamata su un oggetto (ad es. myObj.method()), Questo all'interno del metodo fa riferimento all'oggetto. Laddove questo non sia impostato dalla chiamata o dall'uso di bind , per impostazione predefinita l'oggetto globale (finestra in un browser) o in modalità rigorosa rimane indefinito.
  • JavaScript è un linguaggio orientato agli oggetti, ovvero la maggior parte dei valori sono oggetti, comprese le funzioni. (Stringhe, numeri e valori booleani non sono oggetti.)

Quindi, ecco i frammenti in questione:

var A = function () {
    this.x = function () {
        //do something
    };
};

In questo caso, alla variabile Aviene assegnato un valore che è un riferimento a una funzione. Quando questa funzione viene chiamata utilizzando A(), la funzione è questo non è impostato dalla chiamata in modo che il default è l'oggetto globale e l'espressione this.xè efficace window.x. Il risultato è che viene assegnato un riferimento all'espressione della funzione sul lato destro window.x.

In caso di:

var A = function () { };
A.prototype.x = function () {
    //do something
};

succede qualcosa di molto diverso. Nella prima riga, alla variabile Aviene assegnato un riferimento a una funzione. In JavaScript, tutti gli oggetti funzioni hanno una proprietà prototype per impostazione predefinita, quindi non esiste un codice separato per creare un oggetto A.prototype .

Nella seconda riga, ad A.prototype.x viene assegnato un riferimento a una funzione. Questo creerà una proprietà x se non esiste, o assegnerà un nuovo valore se lo fa. Quindi la differenza con il primo esempio in cui la proprietà x dell'oggetto è coinvolta nell'espressione.

Un altro esempio è di seguito. È simile al primo (e forse a cosa volevi chiedere):

var A = new function () {
    this.x = function () {
        //do something
    };
};

In questo esempio, l' newoperatore è stato aggiunto prima dell'espressione della funzione in modo che la funzione venga chiamata come costruttore. Quando viene chiamato con new, la funzione di questo è impostato per fare riferimento a un nuovo oggetto la cui privata [[Prototype]]proprietà è impostata per fare riferimento pubblico del costruttore del prototipo . Pertanto, nell'istruzione di assegnazione, la xproprietà verrà creata su questo nuovo oggetto. Quando viene chiamata come costruttore, una funzione restituisce questo oggetto per impostazione predefinita, quindi non è necessaria return this;un'istruzione separata .

Per verificare che A abbia una proprietà x :

console.log(A.x) // function () {
                 //   //do something
                 // };

Questo è un uso insolito di nuovo poiché l'unico modo per fare riferimento al costruttore è tramite A.constructor . Sarebbe molto più comune fare:

var A = function () {
    this.x = function () {
        //do something
    };
};
var a = new A();

Un altro modo per ottenere un risultato simile è utilizzare un'espressione di funzione immediatamente invocata:

var A = (function () {
    this.x = function () {
        //do something
    };
}());

In questo caso, Aassegnato il valore restituito di chiamare la funzione sul lato destro. Anche in questo caso, dal momento che questo non è impostato nella chiamata, sarà riferimento all'oggetto globale ed this.xè efficace window.x. Poiché la funzione non restituisce nulla, Aavrà un valore di undefined.

Queste differenze tra i due approcci si manifestano anche se stai serializzando e deserializzando i tuoi oggetti Javascript su / da JSON. I metodi definiti sul prototipo di un oggetto non vengono serializzati quando si serializza l'oggetto, il che può essere utile quando ad esempio si desidera serializzare solo le porzioni di dati di un oggetto, ma non i suoi metodi:

var A = function () { 
    this.objectsOwnProperties = "are serialized";
};
A.prototype.prototypeProperties = "are NOT serialized";
var instance = new A();
console.log(instance.prototypeProperties); // "are NOT serialized"
console.log(JSON.stringify(instance)); 
// {"objectsOwnProperties":"are serialized"} 

Domande correlate :

Sidenote: potrebbe non esserci un significativo risparmio di memoria tra i due approcci, tuttavia l'utilizzo del prototipo per condividere metodi e proprietà probabilmente utilizzerà meno memoria rispetto a ciascuna istanza con la propria copia.

JavaScript non è un linguaggio di basso livello. Potrebbe non essere molto utile pensare alla prototipazione o ad altri modelli di ereditarietà come un modo per cambiare esplicitamente il modo in cui viene allocata la memoria.


49
@keparo: ti sbagli. Ogni oggetto ha un oggetto prototipo [interno] (che può essere null), ma questo è molto diverso dalla prototypeproprietà - che è sulle funzioni e su cui è impostato il prototipo di tutte le istanze quando sono costruite new. Non posso credere che questo abbia ottenuto 87 voti positivi :-(
Bergi,

8
"The language is functional"sei sicuro che questo sia ciò che significa funzionale?
phant0m

23
Dico ciò che @Bergi ha detto sui prototipi. Le funzioni hanno una proprietà prototipo. Tutti gli oggetti, comprese le funzioni, hanno un'altra proprietà interna a cui è possibile accedere con Object.getPrototypeOf (myObject) o con myObject .__ proto__ in alcuni browser. La proprietà proto indica il genitore dell'oggetto nella catena di prototipi (o l'oggetto da cui questo oggetto eredita). La proprietà prototype (che è solo sulle funzioni) indicava l'oggetto che diventerà il genitore di tutti gli oggetti che utilizzano la funzione per creare nuovi oggetti usando la nuova parola chiave.
Jim Cooper,

11
Questo articolo è abbastanza sbagliata e confonde come questo è impostato. Lavorando su una riscrittura.
RobG

37
Questa risposta è piuttosto bizzarra e sembra mancare del tutto il punto della domanda. La domanda sembra essere molto comune sulla definizione delle proprietà del tipo all'interno del costruttore rispetto al protoipo, ma metà della risposta riguarda ciò che accadrebbe se si usasse Auna funzione, e l'altra metà riguarda i modi oscuri e non ortodossi di fare qualcosa di semplice.
JLRishe,

235

Come altri hanno già detto nella prima versione, l'utilizzo di "this" comporta che ogni istanza della classe A abbia una propria copia indipendente del metodo di funzione "x". Considerando che l'uso di "prototipo" significherà che ogni istanza della classe A utilizzerà la stessa copia del metodo "x".

Ecco un po 'di codice per mostrare questa sottile differenza:

// x is a method assigned to the object using "this"
var A = function () {
    this.x = function () { alert('A'); };
};
A.prototype.updateX = function( value ) {
    this.x = function() { alert( value ); }
};

var a1 = new A();
var a2 = new A();
a1.x();  // Displays 'A'
a2.x();  // Also displays 'A'
a1.updateX('Z');
a1.x();  // Displays 'Z'
a2.x();  // Still displays 'A'

// Here x is a method assigned to the object using "prototype"
var B = function () { };
B.prototype.x = function () { alert('B'); };

B.prototype.updateX = function( value ) {
    B.prototype.x = function() { alert( value ); }
}

var b1 = new B();
var b2 = new B();
b1.x();  // Displays 'B'
b2.x();  // Also displays 'B'
b1.updateX('Y');
b1.x();  // Displays 'Y'
b2.x();  // Also displays 'Y' because by using prototype we have changed it for all instances

Come altri hanno già detto, ci sono vari motivi per scegliere un metodo o l'altro. Il mio campione vuole solo dimostrare chiaramente la differenza.


5
Questo è quello che mi aspetterei che accada, ma quando ho istanziato un nuovo oggetto dopo aver cambiato Ax come sopra, visualizzo ancora 'A' a meno che non utilizzi A come un singleton. jsbin.com/omida4/2/edit
jellyfishtree,

19
Questo perché il mio esempio era sbagliato. È sbagliato solo da due anni. Sospiro. Ma il punto è ancora valido. Ho aggiornato l'esempio con uno che funziona davvero. Grazie per segnalarlo.
Benry,

4
È un metodo statico! : D

6
sì ... "prototipo" significa livello statico o di classe ... che sarà condiviso da tutte le istanze create ... mentre "questo" è un metodo di istanza che ogni istanza avrà la propria copia
Aneer Dev

7
Non è statico. Statico, usato nella maggior parte dei linguaggi OO, implica che non vi è alcuna dipendenza thisdall'oggetto, che è il proprietario del metodo. cioè il metodo non ha alcun oggetto che ne sia il proprietario. In questo caso c'è un thisoggetto, come mostrato nella classe A nell'esempio.
CJStuart,

152

Prendi questi 2 esempi:

var A = function() { this.hey = function() { alert('from A') } };

vs.

var A = function() {}
A.prototype.hey = function() { alert('from prototype') };

La maggior parte delle persone qui (in particolare le risposte più votate) hanno cercato di spiegare come sono diverse senza spiegare PERCHÉ. Penso che sia sbagliato e se capisci prima i fondamenti, la differenza diventerà evidente. Proviamo a spiegare prima i fondamenti ...

a) Una funzione è un oggetto in JavaScript. OGNI oggetto in JavaScript ottiene una proprietà interna (nel senso che non puoi accedervi come altre proprietà, tranne forse in browser come Chrome), spesso indicato come __proto__(puoi effettivamente digitare anyObject.__proto__Chrome per vedere a cosa fa riferimento. Questo è solo che , una proprietà, niente di più. Una proprietà in JavaScript = una variabile all'interno di un oggetto, niente di più. Cosa fanno le variabili? Indicano le cose.

Quindi a cosa __proto__punta questa proprietà? Bene, di solito un altro oggetto (spiegheremo perché più avanti). L'unico modo per forzare JavaScript affinché la __proto__proprietà NON punti a un altro oggetto è utilizzare var newObj = Object.create(null). Anche se lo fai, la __proto__proprietà STILL esiste come proprietà dell'oggetto, ma non punta a un altro oggetto, ma punta a null.

Ecco dove la maggior parte delle persone si confonde:

Quando crei una nuova funzione in JavaScript (che è anche un oggetto, ricordi?), Nel momento in cui viene definita, JavaScript crea automaticamente una nuova proprietà su quella funzione chiamata prototype. Provalo:

var A = [];
A.prototype // undefined
A = function() {}
A.prototype // {} // got created when function() {} was defined

A.prototypeè TOTALMENTE DIVERSO dalla __proto__proprietà. Nel nostro esempio, "A" ora ha DUE proprietà chiamate "prototipo" e __proto__. Questa è una grande confusione per le persone. prototypee le __proto__proprietà non sono in alcun modo correlate, sono cose separate che puntano a valori separati.

Potresti chiederti: perché JavaScript ha __proto__proprietà create su ogni singolo oggetto? Bene, una parola: delegazione . Quando chiami una proprietà su un oggetto e l'oggetto non lo possiede, JavaScript cerca l'oggetto a cui fa riferimento __proto__per vedere se lo possiede. Se non ce l'ha, guarda la __proto__proprietà di quell'oggetto e così via ... fino a quando la catena non finisce. Da qui il nome catena prototipo . Ovviamente, se __proto__non punta a un oggetto e invece punta a null, buona fortuna, JavaScript lo capisce e ti restituirà undefinedper la proprietà.

Potresti anche chiederti, perché JavaScript crea una proprietà chiamata prototypeper una funzione quando la definisci? Perché cerca di ingannarti, sì ingannati che funzioni come linguaggi di classe.

Continuiamo con il nostro esempio e creiamo un "oggetto" da A:

var a1 = new A();

C'è qualcosa che sta accadendo in background quando è successa questa cosa. a1è una variabile ordinaria a cui è stato assegnato un nuovo oggetto vuoto.

Il fatto che tu abbia usato l'operatore newprima di una chiamata di funzione ha A()fatto qualcosa di AGGIUNTIVO in background. La newparola chiave ha creato un nuovo oggetto che ora fa riferimento a1e quell'oggetto è vuoto. Ecco cosa succede inoltre:

Abbiamo detto che su ogni definizione di funzione c'è una nuova proprietà creata chiamata prototype(alla quale è possibile accedervi, diversamente dalla __proto__proprietà) creata? Bene, quella proprietà è attualmente in uso.

Quindi ora siamo nel punto in cui abbiamo un a1oggetto vuoto appena sfornato . Abbiamo detto che tutti gli oggetti in JavaScript hanno una __proto__proprietà interna che punta a qualcosa ( a1lo ha anche), sia esso nullo o un altro oggetto. Ciò che l' newoperatore fa è che imposta quella __proto__proprietà in modo che punti alla prototypeproprietà della funzione . Leggi di nuovo. È fondamentalmente questo:

a1.__proto__ = A.prototype;

Abbiamo detto che A.prototypenon è altro che un oggetto vuoto (a meno che non lo cambiamo in qualcos'altro prima di definire a1). Quindi ora sostanzialmente a1.__proto__punta alla stessa cosa A.prototype, che è quell'oggetto vuoto. Entrambi puntano allo stesso oggetto che è stato creato quando si è verificata questa linea:

A = function() {} // JS: cool. let's also create A.prototype pointing to empty {}

Ora, sta succedendo un'altra cosa quando l' var a1 = new A()elaborazione dell'istruzione. Fondamentalmente A()viene eseguito e se A è qualcosa del genere:

var A = function() { this.hey = function() { alert('from A') } };

Tutta quella roba dentro function() { }sta per essere eseguita. Quando raggiungi la this.hey..linea, thisviene modificato in a1e ottieni questo:

a1.hey = function() { alert('from A') }

Non tratterò perché thiscambiano, a1ma questa è un'ottima risposta per saperne di più.

Quindi per riassumere, quando lo fai var a1 = new A()ci sono 3 cose che accadono in background:

  1. Viene creato e assegnato un oggetto vuoto totalmente nuovo a1.a1 = {}
  2. a1.__proto__viene assegnata la proprietà to point alla stessa cosa di A.prototypepoints to (altro oggetto vuoto {})

  3. La funzione A()viene eseguita con thisset per il nuovo oggetto vuoto creato nel passaggio 1 (leggi la risposta a cui ho fatto riferimento in precedenza sul perché thispassa a a1)

Ora proviamo a creare un altro oggetto:

var a2 = new A();

I punti 1,2,3 si ripeteranno. Noti qualcosa? La parola chiave è ripetere. Passaggio 1: a2sarà un nuovo oggetto vuoto, passaggio 2: la sua __proto__proprietà punterà alla stessa cosa A.prototypee, cosa più importante, passaggio 3: la funzione A()viene DI NUOVO eseguita, il che significa che a2otterrà la heyproprietà contenente una funzione. a1e a2hanno due proprietà SEPARATE chiamate heyche indicano 2 funzioni SEPARATE! Ora abbiamo funzioni duplicate negli stessi due oggetti diversi che fanno la stessa cosa, oops ... Puoi immaginare le implicazioni di memoria di questo se abbiamo creato 1000 oggetti new A, dopo che tutte le dichiarazioni di funzioni occupano più memoria di qualcosa come il numero 2. Quindi come possiamo impedirlo?

Ricorda perché la __proto__proprietà esiste su ogni oggetto? In modo che se recuperi la yoManproprietà su a1(che non esiste), __proto__verrà consultata la sua proprietà, che se è un oggetto (e nella maggior parte dei casi lo è), controllerà se contiene yoMane, in caso contrario, consulterà l'oggetto __proto__ecc. Se lo fa, prenderà quel valore di proprietà e te lo mostrerà.

Quindi qualcuno ha deciso di usare questo fatto + il fatto che quando crei a1, la sua __proto__proprietà punta allo stesso oggetto (vuoto) A.prototypee fa questo:

var A = function() {}
A.prototype.hey = function() { alert('from prototype') };

Freddo! Ora, quando crei a1, passa di nuovo attraverso tutti e 3 i passaggi precedenti e nel passaggio 3 non fa nulla, poiché function A()non ha nulla da eseguire. E se lo facciamo:

a1.hey

Vedrà che a1non contiene heye controllerà il suo __proto__oggetto proprietà per vedere se ce l'ha, che è il caso.

Con questo approccio eliminiamo la parte dal passaggio 3 in cui le funzioni sono duplicate su ogni nuova creazione di oggetti. Invece di a1e a2avere un separato heyproprietà, ora nessuno di loro ha. Il che, immagino, ormai hai capito te stesso. Questa è la cosa carina ... se capisci __proto__e Function.prototype, domande come queste saranno piuttosto ovvie.

NOTA: alcune persone tendono a non chiamare la proprietà Prototype interna in quanto __proto__, ho usato questo nome attraverso il post per distinguerlo chiaramente dalla Functional.prototypeproprietà come due cose diverse.


1
Risposta davvero approfondita e istruttiva. Ho fatto alcuni test di memoria usando le strutture degli oggetti sopra (A.prototype.hey vs object this.hey) e ho creato 1000 istanze di ciascuna. L'impronta di memoria per l'approccio della proprietà dell'oggetto era circa 100kb più grande rispetto al prototipo. Ho quindi aggiunto un'altra funzione con lo stesso scopo chiamato "sciocco" e aumentato linearmente a 200kb. Non significativo, ma neanche arachidi.
Jookyone,

La cosa più interessante è che il metodo prototipo era leggermente più lento del metodo della proprietà dell'oggetto eseguito localmente. Nel complesso, non sono sicuro che javascript debba essere utilizzato per la manipolazione dei dati di oggetti con numerazione superiore a 10k, annullando quindi qualsiasi motivo per cambiare approccio basato su potenziali effetti di memoria. A quel punto il lavoro dovrebbe essere scaricato su un server.
Jookyone,

Il punto è __proto__e .prototypesono cose totalmente diverse.
Wayou,

1
Non mi sento soddisfatto solo per darti un voto ... Ben fatto!
Kristianmitk,

58

Nella maggior parte dei casi sono essenzialmente gli stessi, ma la seconda versione consente di risparmiare memoria perché esiste solo un'istanza della funzione anziché una funzione separata per ciascun oggetto.

Un motivo per utilizzare il primo modulo è accedere ai "membri privati". Per esempio:

var A = function () {
    var private_var = ...;

    this.x = function () {
        return private_var;
    };

    this.setX = function (new_x) {
        private_var = new_x;
    };
};

A causa delle regole di scoping di JavaScript, private_var è disponibile per la funzione assegnata a this.x, ma non al di fuori dell'oggetto.


1
Vedi questo post: stackoverflow.com/a/1441692/654708 per un esempio su come accedere ai membri privati ​​tramite prototipi.
GFoley83,

@ GFoley83 che la risposta non mostra questo: i metodi prototipo possono accedere solo alle proprietà "pubbliche" dell'oggetto dato. Solo i metodi privilegiati (non sul prototipo) possono accedere ai membri privati.
Alnitak,

27

Il primo esempio modifica l'interfaccia solo per quell'oggetto. Il secondo esempio cambia l'interfaccia per tutti gli oggetti di quella classe.


Entrambi renderanno xdisponibile la funzione per tutti gli oggetti il ​​cui prototipo è assegnato a una nuova istanza di A:function B () {}; B.prototype = new A(); var b = new B(); b.x() // Will call A.x if A is defined by first example;
Spencer Williams

21

Il problema finale con l'utilizzo thisinvece di prototypeè che quando si sovrascrive un metodo, il costruttore della classe base farà ancora riferimento al metodo sovrascritto. Considera questo:

BaseClass = function() {
    var text = null;

    this.setText = function(value) {
        text = value + " BaseClass!";
    };

    this.getText = function() {
        return text;
    };

    this.setText("Hello"); // This always calls BaseClass.setText()
};

SubClass = function() {
    // setText is not overridden yet,
    // so the constructor calls the superclass' method
    BaseClass.call(this);

    // Keeping a reference to the superclass' method
    var super_setText = this.setText;
    // Overriding
    this.setText = function(value) {
        super_setText.call(this, "SubClass says: " + value);
    };
};
SubClass.prototype = new BaseClass();

var subClass = new SubClass();
console.log(subClass.getText()); // Hello BaseClass!

subClass.setText("Hello"); // setText is already overridden
console.log(subClass.getText()); // SubClass says: Hello BaseClass!

contro:

BaseClass = function() {
    this.setText("Hello"); // This calls the overridden method
};

BaseClass.prototype.setText = function(value) {
    this.text = value + " BaseClass!";
};

BaseClass.prototype.getText = function() {
    return this.text;
};

SubClass = function() {
    // setText is already overridden, so this works as expected
    BaseClass.call(this);
};
SubClass.prototype = new BaseClass();

SubClass.prototype.setText = function(value) {
    BaseClass.prototype.setText.call(this, "SubClass says: " + value);
};

var subClass = new SubClass();
console.log(subClass.getText()); // SubClass says: Hello BaseClass!

Se ritieni che questo non sia un problema, dipende dal fatto che puoi vivere senza variabili private e se hai esperienza sufficiente per conoscere una perdita quando ne vedi una. Inoltre, dover mettere la logica del costruttore dopo le definizioni del metodo è scomodo.

var A = function (param1) {
    var privateVar = null; // Private variable

    // Calling this.setPrivateVar(param1) here would be an error

    this.setPrivateVar = function (value) {
        privateVar = value;
        console.log("setPrivateVar value set to: " + value);

        // param1 is still here, possible memory leak
        console.log("setPrivateVar has param1: " + param1);
    };

    // The constructor logic starts here possibly after
    // many lines of code that define methods

    this.setPrivateVar(param1); // This is valid
};

var a = new A(0);
// setPrivateVar value set to: 0
// setPrivateVar has param1: 0

a.setPrivateVar(1);
//setPrivateVar value set to: 1
//setPrivateVar has param1: 0

contro:

var A = function (param1) {
    this.setPublicVar(param1); // This is valid
};
A.prototype.setPublicVar = function (value) {
    this.publicVar = value; // No private variable
};

var a = new A(0);
a.setPublicVar(1);
console.log(a.publicVar); // 1

20

Ogni oggetto è collegato a un oggetto prototipo. Quando si tenta di accedere a una proprietà che non esiste, JavaScript cercherà l'oggetto prototipo dell'oggetto per quella proprietà e la restituirà se esiste.

La prototypeproprietà di un costruttore di funzioni si riferisce all'oggetto prototipo di tutte le istanze create con quella funzione durante l'utilizzo new.


Nel tuo primo esempio, stai aggiungendo una proprietà xa ciascuna istanza creata con la Afunzione.

var A = function () {
    this.x = function () {
        //do something
    };
};

var a = new A();    // constructor function gets executed
                    // newly created object gets an 'x' property
                    // which is a function
a.x();              // and can be called like this

Nel secondo esempio si aggiunge una proprietà all'oggetto prototipo a cui Apuntano tutte le istanze create .

var A = function () { };
A.prototype.x = function () {
    //do something
};

var a = new A();    // constructor function gets executed
                    // which does nothing in this example

a.x();              // you are trying to access the 'x' property of an instance of 'A'
                    // which does not exist
                    // so JavaScript looks for that property in the prototype object
                    // that was defined using the 'prototype' property of the constructor

In conclusione, nel primo esempio viene assegnata una copia della funzione a ciascuna istanza . Nel secondo esempio una singola copia della funzione è condivisa da tutte le istanze .


1
Votato questo per essere la risposta più diretta alla domanda.
Nick Pineda,

1
Mi è piaciuto il tuo approccio diretto !! pollici in su!
Prince Vijay Pratap,

16

Qual è la differenza? => Molto.

Penso che la thisversione sia utilizzata per abilitare l'incapsulamento, ovvero nascondere i dati. Aiuta a manipolare le variabili private.

Diamo un'occhiata al seguente esempio:

var AdultPerson = function() {

  var age;

  this.setAge = function(val) {
    // some housekeeping
    age = val >= 18 && val;
  };

  this.getAge = function() {
    return age;
  };

  this.isValid = function() {
    return !!age;
  };
};

Ora, la prototypestruttura può essere applicata come segue:

Adulti diversi hanno età diverse, ma tutti gli adulti hanno gli stessi diritti.
Quindi, lo aggiungiamo usando il prototipo, piuttosto che questo.

AdultPerson.prototype.getRights = function() {
  // Should be valid
  return this.isValid() && ['Booze', 'Drive'];
};

Vediamo ora l'implementazione.

var p1 = new AdultPerson;
p1.setAge(12); // ( age = false )
console.log(p1.getRights()); // false ( Kid alert! )
p1.setAge(19); // ( age = 19 )
console.log(p1.getRights()); // ['Booze', 'Drive'] ( Welcome AdultPerson )

var p2 = new AdultPerson;
p2.setAge(45);    
console.log(p2.getRights()); // The same getRights() method, *** not a new copy of it ***

Spero che sia di aiuto.


3
+1 Una risposta molto meno contorta e più grafica delle altre. Ma dovresti elaborare un po 'di più prima di fornire questi (buoni) esempi.
yerforkferchips

1
Non sono sicuro di "questa versione viene utilizzata per abilitare l'incapsulamento, ovvero il nascondere i dati". Se una proprietà all'interno di una funzione viene definita utilizzando "this" come in "this.myProperty = ...", tale proprietà non è "privata" e è possibile accedervi da oggetti esterni alla classe utilizzando "new".
NoChance,

14

Il prototipo è il modello della classe; che si applica a tutte le sue istanze future. Considerando che questa è l'istanza particolare dell'oggetto.


14

So che è stata data una risposta a morte, ma vorrei mostrare un esempio reale delle differenze di velocità.

Funziona direttamente sull'oggetto

Funzione sul prototipo

Qui stiamo creando 2.000.000 di nuovi oggetti con un printmetodo in Chrome. Stiamo memorizzando ogni oggetto in un array. L'inserimento printdel prototipo richiede circa 1/2 del tempo.


13

Lascia che ti dia una risposta più completa che ho imparato durante un corso di formazione JavaScript.

La maggior parte delle risposte ha già menzionato la differenza, ovvero quando si prototipa la funzione è condivisa con tutte le (future) istanze. Considerando che la dichiarazione della funzione nella classe creerà una copia per ogni istanza.

In generale non c'è giusto o sbagliato, è più una questione di gusti o una decisione di progettazione a seconda delle esigenze. Il prototipo tuttavia è la tecnica che viene utilizzata per sviluppare in modo orientato agli oggetti, come spero che vedrai alla fine di questa risposta.

Hai mostrato due schemi nella tua domanda. Proverò a spiegarne altre due e cercherò di spiegare le differenze se rilevanti. Sentiti libero di modificare / estendere. In tutti gli esempi si tratta di un oggetto auto che ha una posizione e può muoversi.

Modello Decoratore di oggetti

Non sono sicuro che questo modello sia ancora rilevante al giorno d'oggi, ma esiste. Ed è bene saperlo. È sufficiente passare un oggetto e una proprietà alla funzione decoratore. Il decoratore restituisce l'oggetto con proprietà e metodo.

var carlike = function(obj, loc) {
    obj.loc = loc;
    obj.move = function() {
        obj.loc++;
    };
    return obj;
};

var amy = carlike({}, 1);
amy.move();
var ben = carlike({}, 9);
ben.move();

Classi funzionali

Una funzione in JavaScript è un oggetto specializzato. Oltre ad essere invocata, una funzione può memorizzare proprietà come qualsiasi altro oggetto.

In questo caso Carè una funzione ( pensa anche all'oggetto ) che può essere invocata come al solito. Ha una proprietà methods(che è un oggetto con una movefunzione). Quando Carviene invocata extend, viene chiamata la funzione, che fa un po 'di magia, ed estende la Carfunzione (pensa all'oggetto) con i metodi definiti all'interno methods.

Questo esempio, sebbene diverso, si avvicina di più al primo esempio nella domanda.

var Car = function(loc) {
    var obj = {loc: loc};
    extend(obj, Car.methods);
    return obj;
};

Car.methods = {
    move : function() {
        this.loc++;
    }
};

var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();

Classi prototipali

I primi due schemi consentono una discussione sull'uso delle tecniche per definire metodi condivisi o sull'uso di metodi definiti in linea nel corpo del costruttore. In entrambi i casi ogni istanza ha la sua movefunzione.

Il modello prototipo non si presta bene allo stesso esame, poiché la condivisione delle funzioni tramite una delegazione prototipo è l'obiettivo stesso del modello prototipo. Come altri hanno sottolineato, si prevede che abbia un footprint di memoria migliore.

Tuttavia, c'è un punto interessante da sapere: ogni prototypeoggetto ha una proprietà di convenienza constructor, che rimanda alla funzione (pensa all'oggetto) a cui è stata assegnata.

Per quanto riguarda le ultime tre righe:

In questo esempio Carcollegamenti alla prototypeoggetto, che collega via constructoralla Carstessa, cioè Car.prototype.constructorè Caresso stesso. Ciò consente di capire quale funzione di costruzione ha creato un determinato oggetto.

amy.constructorLa ricerca fallisce e quindi viene delegata Car.prototype, che ha la proprietà di costruzione. E così amy.constructorè Car.

Inoltre, amy è un instanceof Car. L' instanceofoperatore funziona verificando se l'oggetto prototipo ( Car) dell'operando destro si trova in qualsiasi punto della amycatena prototype ( ) dell'operando sinistro .

var Car = function(loc) {
    var obj = Object.create(Car.prototype);
    obj.loc = loc;
    return obj;
};

Car.prototype.move = function() {
        this.loc++;
};

var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();

console.log(Car.prototype.constructor);
console.log(amy.constructor);
console.log(amy instanceof Car);

Alcuni sviluppatori possono essere confusi all'inizio. Vedi l'esempio seguente:

var Dog = function() {
  return {legs: 4, bark: alert};
};

var fido = Dog();
console.log(fido instanceof Dog);

Il instanceof operatore ritorna false, perché Dogil prototipo del modello non può essere trovato da nessuna parte nella fidocatena del prototipo. fidoè un semplice oggetto che viene creato con un oggetto letterale, cioè solo a delega Object.prototype.

Modelli pseudoclassici

Questa è davvero solo un'altra forma del modello prototipo in forma semplificata e più familiare per fare coloro che programmano in Java, ad esempio, poiché utilizza il newcostruttore.

Fa esattamente lo stesso del modello prototipo, è solo uno zucchero sintattico sopra il modello prototipo.

Tuttavia, la differenza principale è che ci sono ottimizzazioni implementate nei motori JavaScript che si applicano solo quando si utilizza il modello pseudoclassico. Pensa al modello pseudoclassico una versione probabilmente più veloce del modello prototipo; le relazioni dell'oggetto in entrambi gli esempi sono le stesse.

var Car = function(loc) {
    this.loc = loc;
};

Car.prototype.move = function() {
        this.loc++;
};

var amy = new Car(1);
amy.move();
var ben = new Car(9);
ben.move();

Infine, non dovrebbe essere troppo difficile capire come si possa fare una programmazione orientata agli oggetti. Ci sono due sezioni.

Una sezione che definisce proprietà / metodi comuni nel prototipo (catena).

E un'altra sezione in cui metti le definizioni che distinguono gli oggetti l'uno dall'altro (loc variabile negli esempi).

Questo è ciò che ci consente di applicare concetti come superclasse o sottoclasse in JavaScript.

Sentiti libero di aggiungere o modificare. Ancora una volta completo, potrei rendere questo un wiki della comunità forse.


Per non dare un incarico molto approfondito, ma ho pensato che OO e l'eredità prototipica fossero essenzialmente diverse scuole di pensiero.
Nick Pineda,

Lo sono, ma si può "fare OO" con tecniche / pensieri diversi, no?
Ely,

Non ne sono sicuro. Molti dicono solo che la filosofia prototipica è solo diversa e molti cercano di confrontarla con OO perché è la scuola di pensiero a cui molti sono abituati.
Nick Pineda,

Voglio dire, se vuoi praticare lo stile OO e il linguaggio offre una serie di tecniche che aiutano a farlo, non è necessariamente sbagliato.
Ely,

11

Credo che @Matthew Crumley abbia ragione. Sono funzionalmente , se non strutturalmente, equivalenti. Se usi Firebug per guardare gli oggetti creati usando new, puoi vedere che sono gli stessi. Tuttavia, la mia preferenza sarebbe la seguente. Immagino che sembra più simile a quello a cui sono abituato in C # / Java. Cioè, definire la classe, definire i campi, il costruttore e i metodi.

var A = function() {};
A.prototype = {
    _instance_var: 0,

    initialize: function(v) { this._instance_var = v; },

    x: function() {  alert(this._instance_var); }
};

EDIT Non intendevo implicare che l'ambito della variabile fosse privato, stavo solo cercando di illustrare come definisco le mie classi in javascript. Il nome della variabile è stato modificato per riflettere questo.


2
_instance_var come nella proprietà initializee x methods do not refer to the _instance_var` su Aun'istanza, ma su una globale. Utilizzare this._instance_varse si intendeva utilizzare la _instance_varproprietà di Aun'istanza.
Lekensteyn,

2
La cosa divertente è che anche Benry ha commesso un simile errore, che è stato scoperto anche dopo due anni: p
Lekensteyn,

10

Come discusso in altre risposte, è davvero una considerazione delle prestazioni perché la funzione nel prototipo è condivisa con tutte le istanze - piuttosto che la funzione creata per ogni istanza.

Ho messo insieme un jsperf per mostrarlo. C'è una differenza drammatica nel tempo impiegato per creare un'istanza della classe, anche se è davvero rilevante solo se stai facendo molte istanze.

http://jsperf.com/functions-in-constructor-vs-prototype


8

Pensa al linguaggio tipicamente statico, le cose su prototypesono statiche e le cose su thissono correlate all'istanza.

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.