Gli esempi di codice nella domanda riflettono un paio di incomprensioni. Per prima cosa risolviamoli:
- classe è una parola chiave riservata in Javascript. Non è possibile utilizzare la classe come nome di qualsiasi variabile o funzione. Ciò non è dovuto al fatto che il linguaggio Javascript fa alcun uso della parola chiave, ma perché è stato pianificato per un possibile uso futuro.
- non ci sono classi reali in Javascript.Ciò che potresti aver visto, è una varietà di tentativi di simulare l'ereditarietà della classe utilizzando il meccanismo di ereditarietà disponibile in Javascript, basato su oggetti prototipo, condivisione di proprietà con istanze collegate
- molto importante: la proprietà prototipo utilizzata in questo meccanismo di ereditarietà è impostato su un funzione, non direttamente su oggetti
citare Douglas Crockford nel Capitolo 5, ereditarietà, di JavaScript: The Good Parts:
Invece di avere oggetti ereditano direttamente da altri oggetti, un inutile livello di riferimento indiretto è inserito in modo tale che gli oggetti sono prodotte da funzioni di costruzione.
(...)
Quando una funzione viene richiamata con il modello invocazione costruttore utilizzando il nuovo prefisso, questo modifica il modo in cui viene eseguita la funzione.
Douglas Crockford spiega quindi come il nuovo operatore possa essere implementato come funzione JavaScript. Questa funzione si avvale di numerose altre funzioni definite nel libro, così ho riscritto in un (un po ') forma più semplice di seguito:
function createNew(constructor) {
// a function to explain the new operator:
// var object = createNew(constructor);
// is equivalent to
// var object = new constructor();
//
// param: constructor, a function
// return: a new instance of the "constructor" kind of objects
// step 1. create a new empty object instance
// linked to the prototype of provided constructor
var hiddenLink = function(){};
hiddenLink.prototype = constructor.prototype;
var instance = new hiddenLink(); // cheap trick here: using new to implement new
// step 2. apply the constructor the new instance and get the result
var result = constructor.apply(object); // make this a reference to instance within constructor
// step 3. check the result, and choose whether to return it or the created instance
if (typeof result === 'object') {
return object;
} else {
return instance;
}
}
In inglese semplice, se si chiama new constructor()
, dove costruttore è una funzione, l'operatore crea un nuovo oggetto con un collegamento per ereditare proprietà dal costruttore, applica la funzione di costruzione ad esso e restituisce il valore restituito dal costruttore o il nuovo oggetto nel caso in cui il costruttore restituisca qualcos'altro che non è un oggetto.
In qualsiasi momento, prima o dopo la creazione di nuove istanze utilizzando un costruttore personalizzato, è possibile modificare il prototipo (oggetto) sul costruttore (funzione):
function constructor(){} // the most simple constructor function: does nothing
var before = new constructor();
var different = new constructor();
different.answer = "So long, and thanks for all the fish";
constructor.prototype = {}; // set an empty object to the prototype property
constructor.prototype.answer = 42; // create a new property on prototype object
constructor.prototype.answer = Math.PI; // replace an existing property
var after = new constructor();
Attraverso il link nascosto aggiunto a tutti gli oggetti creati usando questo costruttore (vedi "trucco economico" in createNew), è possibile accedere alle proprietà dell'oggetto prototipo su tutte queste istanze, a meno che non vengano sovrascritte dalle proprietà definite direttamente sugli oggetti.
before.answer === Math.PI; // true
after.answer === Math.PI; // true
different.answer === "So long, and thanks for all the fish"; // true
Usando questa conoscenza appena acquisita, come si dovrebbe creare una nuova "classe" di oggetti che ereditano tutte le proprietà degli array, insieme ad un nuovo metodo vuoto() per rimuovere tutti gli elementi?
Primo, non ci sono classi in Javascript, quindi per creare una nuova "classe", devo definire una nuova funzione di costruzione. Chiamiamolo CustomArray, con una C maiuscola per seguire la convenzione che le funzioni del costruttore dovrebbero iniziare con una maiuscola.
function CustomArray(){}
ora posso creare istanze personalizzati:
var myArray = new CustomArray();
In secondo luogo, voglio istanze create con CustomArray di ereditare le proprietà di matrice:
myArray.prototype = new Array(); // WRONG EXAMPLE: we must set CustomArray.prototype
CustomArray.prototype = Array; // WRONG EXAMPLE: prototype expects an object, Array is a function
CustomArray.prototype = new Array(); // OK, BUT: the simpler form [] should be used instead
CustomArray.prototype = [];
Terzo, voglio che tutti istanze create con CustomArray per avere il metodo empty():
function empty(){
// empty this array by setting its length to 0
// function to be called in the context (this) of an array
this.length = 0;
}
CustomArray.prototype.empty = empty; // set the function named empty to the property "empty" of the prototype
Infine, posso riscrivere l'esempio in modo più conciso:
function CustomArray(){}
CustomArray.prototype = [];
CustomArray.prototype.empty = function(){ this.length = 0; }
posso ora creare una matrice personalizzata, impostare una coppia di valori e svuotarlo:
var myArray = new CustomArray();
myArray[0] = "abc";
myArray[1] = "def";
myArray[2] = "ghi";
myArray.empty();
Il problema è: il codice sopra riportato non funziona come previsto. Perché? Perché diversamente dagli array regolari, l'impostazione dei valori nel nostro array personalizzato non aumenta automagicamente la proprietà length dell'array. Allo stesso modo, chiamando empty() imposta solo la proprietà length del nostro array personalizzato su 0, non cancella tutti i valori all'interno.
Inoltre, non abbiamo potuto usare la sintassi letterale di Array per inizializzare la nostra gamma personalizzato:
var myArray = ["abc","def","ghi"]; // this creates a regular array
Tutto sommato, è importante capire come funziona JavaScript eredità, ma spesso si possono trovare meno utile del previsto e ci sono modi più semplici per ottenere lo stesso risultato, ad esempio utilizzando le funzioni di builder anziché i costruttori. Possiamo risolvere il problema utilizzando una funzione costruttore customizeArray estendere array regolari:
function customizeArray(array){
array.empty = function(){
this.length = 0;
};
}
var myArray = ["abc","def","ghi"];
customizeArray(myArray);
myArray.empty();
Questo codice funziona come previsto perché myArray è una serie regolare in questo caso, esteso con un nuovo metodo denominato vuoto. Il vantaggio principale e l'inconveniente di questo approccio rispetto all'uso dei prototipi è che modifica solo l'istanza selezionata e, se si lavorasse con molti oggetti simili nello stesso momento, l'impostazione di questa proprietà aggiuntiva avrebbe utilizzato più memoria rispetto all'impostazione di una singola condivisione proprietà su un prototipo comune.
Per evitare questo, si può essere tentati di modificare il prototipo Array direttamente:
Array.prototype.empty = function(){
this.length = 0;
};
var myArray = ["abc","def","ghi"];
myArray.empty();
Funziona, ma vorrei consigliare contro di essa: si collega una proprietà personalizzata a ogni istanza di matrice , compresi tutti quelli creati da quelle librerie di fantasia, plugin, framework, script pubblicitari e di analisi, tutto il codice sulla tua pagina. Inutile dire che potrebbe rompere qualcosa in un posto dove non è possibile risolverlo.
Edit: interessante post sul blog di kangax come follow-up: "How ECMAScript 5 still does not allow to subclass an array"
' function() subclass() {} '? – Leven