2010-01-22 20 views
6

Ho un'istanza di sottoclassi in JavaScript utilizzandosubclass.prototype = new superclasse() vs. sottoclasse = new superclasse()

object = new class() 

ma noto alcune persone istanziare utilizzando

object.prototype = new class() 

domanda : Qual è la differenza? A me sembra che quest'ultimo stia rispettando la catena ereditaria più perché se class() ha un gruppo di istruzioni "this.variable = x" e l'oggetto è qualcosa che si desidera ereditare da esso piuttosto che un'istanza di classe, si assegnano accuratamente quelle variabili all'oggetto prototipo piuttosto che all'oggetto stesso come nel primo caso. Quindi in effetti è così?

object = new class() |vs.| subclass.prototype = new superclass() 

Tuttavia, funzionalmente nel programma di entrambi sono la stessa cosa?

Domanda laterale: anche io sono un po 'oscuro su ciò che l'operatore new effettivamente fa. Mi sembra di fare qualcosa come creare un oggetto vuoto e assegnargli la proprietà proto?

risposta

5

La differenza è che quando si fa:

var subclass = new superclass(); 

si sta creando un'istanza di superclass. subclass è solo variabile. Non stai creando una sottoclasse (ad esempio, rendendo subclass ereditario superclass). Nell'ultimo esempio, supponendo che la sottoclasse sia una funzione, si sta dicendo che tutte le nuove istanze di sottoclasse dovrebbero ereditare (ad es. Sottoclasse) superclass.

Quindi:

function superclass() {this.stuff="stuff";} 
function subclass() {} 
subclass.prototype = new superclass(); 
alert(new subclass().this); // pops up "stuff" 

è eredità prototipo.

Come per l'operatore new, viene utilizzato per creare un'istanza di oggetti incorporati e tipi definiti dall'utente. Un tipo definito dall'utente è semplicemente una funzione.

Edit: Quando ho scritto sopra che sottoclasse eredita supertipo utilizzando l'ereditarietà prototipo, voglio dire che tutte le nuove istanze di sottoclasse ereditano da uno particolare esempio della superclasse, non dal superclass tipo/funzione stessa.

+0

' function() subclass() {} '? – Leven

12

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"

+0

Grazie, Eric! Sì, sono a conoscenza dei primi due fraintendimenti - ho letto tramite JS: The Good Parts, tuttavia sto cercando di usare l'ereditarietà in un modo tale che le funzioni agiscono su variabili di istanza e non è stato in grado di farlo con il seguente esempio di Crockford. Ti invierò un'email di follow-up a causa del limite di caratteri. Ancora una volta, grazie mille! – ambertch

2

Condividere una demo veloce di Javascript eredità dopo la lettura del mozilla doc

function Employee (name, dept) { 
    this.name = name || ""; 
    this.dept = dept || ""; 
} 

function Programmer (name, projs) { 
    Employee.call(this, name, "programming"); 
    this.projects = projs || []; 
} 
Programmer.prototype = new Employee; 

// demo dynamic inheritance 
Employee.prototype.leave = 10; 

var johnny = new Programmer("Johnny", ["C#","Java"]); 
alert("name: " + johnny.name + "\n" 
     + "dept: " + johnny.dept + "\n" 
     + "projects: " + johnny.projects + "\n" 
     + "leave: " + johnny.leave); 

var mary = new Programmer("Mary", ["Javascript","Java"]); 
alert("name: " + mary.name + "\n" 
     + "dept: " + mary.dept + "\n" 
     + "projects: " + mary.projects + "\n" 
     + "leave: " + mary.leave); 

alert("changing leave of all staff to 8"); 
Employee.prototype.leave = 8; 
alert("Johnny leave: " + johnny.leave); // 8 
alert("Mary leave: " + mary.leave); // 8 

alert("cannot batch move staff to another department"); 
Employee.prototype.dept = "sales"; 
alert("Johnny dept: " + johnny.dept); // programming 
alert("Mary dept: " + mary.dept); // programming 

Demo in jsfiddle Demo with more debug in jsfiddle

Problemi correlati