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.
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
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 myObject
il 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!
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.
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 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.
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 Stringa
Il 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.
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.