Prototipi in JavaScript

Quando si definisce una funzione in JavaScript, viene fornita con alcune proprietà predefinite; uno di questi è il prototipo illusorio. In questo articolo, descriverò cosa è e perché dovresti usarlo nei tuoi progetti.


Cos'è il prototipo?

La proprietà prototype è inizialmente un oggetto vuoto e può avere membri aggiunti ad esso, come qualsiasi altro oggetto.

var myObject = function (name) this.name = name; restituiscilo; ; console.log (typeof myObject.prototype); // oggetto myObject.prototype.getName = function () return this.name; ;

Nel frammento sopra, abbiamo creato una funzione, ma se chiamiamo myObject (), semplicemente restituirà il finestra oggetto, perché è stato definito nell'ambito globale. Questo restituirà quindi l'oggetto globale, poiché non è stato ancora istanziato (più su questo più avanti).

console.log (myObject () === finestra); // vero

Il collegamento segreto

Ogni oggetto all'interno di JavaScript ha una proprietà "segreta".

Prima di continuare, vorrei discutere il collegamento "segreto" che rende il prototipo funzionante come fa.

Ogni oggetto all'interno di JavaScript ha una proprietà "segreta" aggiunta ad esso quando viene definito o istanziato, denominato __proto__; questo è come si accede alla catena del prototipo. Tuttavia, non è una buona idea accedere __proto__ all'interno dell'applicazione, in quanto non è disponibile in tutti i browser.

Il __proto__ la proprietà non deve essere confusa con il prototipo di un oggetto, in quanto sono due proprietà separate; detto questo, vanno di pari passo. È importante fare questa distinzione, in quanto può essere abbastanza confuso all'inizio! Cosa significa esattamente? Lasciatemi spiegare. Quando abbiamo creato il myObject funzione, stavamo definendo un oggetto di tipo Funzione.

console.log (typeof myObject); // funzione

Per quelli inconsapevoli, Funzione è un oggetto predefinito in JavaScript e, di conseguenza, ha le sue proprietà (ad es. lunghezza e argomenti) e metodi (ad es. chiamata e applicare). E sì, anche questo ha il suo oggetto prototipo, così come il segreto __proto__ collegamento. Ciò significa che, da qualche parte all'interno del motore JavaScript, c'è un po 'di codice che potrebbe essere simile al seguente:

 Function.prototype = argomenti: null, lunghezza: 0, call: function () // codice segreto, applica: function () // codice segreto ...

In verità, probabilmente non sarebbe così semplicistico; questo è solo per illustrare come funziona la catena di prototipi.

Quindi abbiamo definito myObject come una funzione e dato un argomento, nome; ma non abbiamo mai impostato alcuna proprietà, ad esempio lunghezza o metodi, come chiamata. Quindi, perché il seguente lavoro?

console.log (myObject.length); // 1 (essendo la quantità di argomenti disponibili)

Questo perché, quando abbiamo definito myObject, ha creato a __proto__ proprietà e impostare il suo valore a Function.prototype (illustrato nel codice sopra). Quindi, quando accediamo myObject.length, cerca una proprietà di myObject chiamato lunghezza e non ne trova uno; poi viaggia lungo la catena, attraverso il __proto__ link, trova la proprietà e la restituisce.

Potresti chiederti perché lunghezza è impostato per 1 e non 0 - o qualsiasi altro numero per questo fatto. Questo è perché myObject è in realtà un esempio di Funzione.

console.log (myObject instanceof Function); // true console.log (myObject === Funzione); // falso

Quando viene creata un'istanza di un oggetto, il __proto__ la proprietà viene aggiornata per puntare al prototipo del costruttore, che, in questo caso, è Funzione.

console.log (myObject .__ proto__ === Function.prototype) // true

Inoltre, quando si crea un nuovo Funzione oggetto, il codice nativo all'interno del Funzione costruttore valuterà il numero di argomenti e l'aggiornamento this.length di conseguenza, che, in questo caso, è 1.

Se, tuttavia, creiamo una nuova istanza di myObject usando il nuovo parola chiave, __proto__ punterà a myObject.prototype come myObject è il costruttore della nostra nuova istanza.

 var myInstance = new myObject ("pippo"); console.log (myInstance .__ proto__ === myObject.prototype); // vero

Oltre ad avere accesso ai metodi nativi all'interno di Funzione.prototipo, come chiamata e applicare, ora abbiamo accesso a myObjectil metodo, getName.

 console.log (myInstance.getName ()); // foo var mySecondInstance = new myObject ("bar"); console.log (mySecondInstance.getName ()); // bar console.log (myInstance.getName ()); // pippo

Come puoi immaginare, questo è abbastanza utile, in quanto può essere utilizzato per la cianografia di un oggetto e creare tutte le istanze necessarie, il che mi porta al prossimo argomento!


Perché usare Prototype Better?

Supponiamo, ad esempio, che stiamo sviluppando un gioco su tela e che occorra allo stesso tempo diversi (possibilmente centinaia) oggetti sullo schermo. Ogni oggetto richiede le sue proprietà, come ad esempio X e y coordinate, larghezza,altezza, e molti altri.

Potremmo farlo come segue:

 var GameObject1 = x: Math.floor ((Math.random () * myCanvasWidth) + 1), y: Math.floor ((Math.random () * myCanvasHeight) + 1), width: 10, height: 10, draw: function () myCanvasContext.fillRect (this.x, this.y, this.width, this.height);  ...; var GameObject2 = x: Math.floor ((Math.random () * myCanvasWidth) + 1), y: Math.floor ((Math.random () * myCanvasHeight) + 1), width: 10, height: 10, draw: function () myCanvasContext.fillRect (this.x, this.y, this.width, this.height);  ...;

... fai questo 98 altre volte ...

Ciò che questo farà è creare tutti questi oggetti all'interno della memoria, il tutto con definizioni separate per metodi come disegnare e qualunque altro metodo possa essere richiesto. Questo non è certamente l'ideale, dato che il gioco gonfierà i browser allocando la memoria JavaScript, e lo farà girare molto lentamente ... o addirittura smettere di rispondere.

Anche se questo probabilmente non succederebbe con soli 100 oggetti, può comunque essere piuttosto un successo in termini di prestazioni, poiché dovrà cercare cento oggetti diversi, piuttosto che il singolo prototipo oggetto.


Come usare Prototype

Per rendere l'applicazione più veloce (e seguire le migliori pratiche), possiamo (ri) definire la proprietà prototipo di GameObject; ogni istanza di GameObject farà quindi riferimento ai metodi all'interno GameObject.prototype come se fossero i loro metodi.

 // definisce la funzione di costruzione di GameObject var GameObject = function (width, height) this.x = Math.floor ((Math.random () * myCanvasWidth) + 1); this.y = Math.floor ((Math.random () * myCanvasHeight) + 1); this.width = width; questa altezza = altezza; restituiscilo; ; // (re) definisce l'oggetto prototipo GameObject GameObject.prototype = x: 0, y: 0, width: 5, width: 5, draw: function () myCanvasContext.fillRect (this.x, this.y, this .width, this.height); ;

Possiamo quindi istanziare il GameObject 100 volte.

 var x = 100, arrayOfGameObjects = []; do arrayOfGameObjects.push (new GameObject (10, 10));  while (x--);

Ora abbiamo una serie di 100 GameObjects, che condividono tutti lo stesso prototipo e la definizione di disegnare metodo, che salva drasticamente la memoria all'interno dell'applicazione.

Quando chiamiamo il disegnare metodo, farà riferimento alla stessa identica funzione.

 var GameLoop = function () for (gameObject in arrayOfGameObjects) gameObject.draw (); ;

Il prototipo è un oggetto vivo

Il prototipo di un oggetto è un oggetto vivo, per così dire. Questo significa semplicemente che, se, dopo aver creato tutte le istanze di GameObject, decidiamo che, invece di disegnare un rettangolo, vogliamo disegnare un cerchio, possiamo aggiornare il nostro GameObject.prototype.draw metodo di conseguenza.

 GameObject.prototype.draw = function () myCanvasContext.arc (this.x, this.y, this.width, 0, Math.PI * 2, true); 

E ora, tutti i precedenti casi di GameObject e tutte le istanze future disegneranno un cerchio.


Aggiornamento dei prototipi di oggetti nativi

Sì, questo è possibile. Potresti avere familiarità con le librerie JavaScript, come Prototype, che sfruttano questo metodo.

Usiamo un semplice esempio:

 String.prototype.trim = function () return this.replace (/ ^ \ s + | \ s + $ / g, ");;

Ora possiamo accedere a questo come metodo di qualsiasi stringa:

"Foo bar" .trim (); // "foo bar"

C'è un aspetto negativo secondario a questo, tuttavia. Ad esempio, puoi usare questo nella tua applicazione; ma un anno o due lungo la strada, un browser può implementare una versione aggiornata di JavaScript che include un nativo tagliare metodo all'interno del StringaIl prototipo. Questo significa che la tua definizione di tagliare sostituirà la versione nativa! Yikes! Per superare questo, possiamo aggiungere un semplice controllo prima di definire la funzione.

 if (! String.prototype.trim) String.prototype.trim = function () return this.replace (/ ^ \ s + | \ s + $ / g, ");;

Ora, se esiste, userà la versione nativa di tagliare metodo.

Come regola generale, è generalmente considerata una best practice per evitare l'estensione di oggetti nativi. Ma, come con qualsiasi cosa, le regole possono essere infrante, se necessario.


Conclusione

Si spera che questo articolo faccia luce sulla spina dorsale di JavaScript che è un prototipo. Ora dovresti essere sulla buona strada per creare applicazioni più efficienti.

Se hai domande sul prototipo, fammelo sapere nei commenti, e farò del mio meglio per rispondere.