2011-11-11 17 views
35

Considerare il seguente codice.Eredità JavaScript e la proprietà del costruttore

function a() {} 
function b() {} 
function c() {} 

b.prototype = new a(); 
c.prototype = new b(); 

console.log((new a()).constructor); //a() 
console.log((new b()).constructor); //a() 
console.log((new c()).constructor); //a() 
  • Perché non è il costruttore aggiornato per B e C?
  • Sto facendo ereditarietà errata?
  • Qual è il modo migliore per aggiornare il costruttore?

Inoltre, considerare quanto segue.

console.log(new a() instanceof a); //true 
console.log(new b() instanceof b); //true 
console.log(new c() instanceof c); //true 
  • Dato che (new c()).constructor è uguale a a() e Object.getPrototypeOf(new c()) è a{ }, come è possibile per instanceof sapere che new c() è un'istanza di c?

http://jsfiddle.net/ezZr5/

+3

Qualsiasi motivo per cui è necessario aggiornare il costruttore? Trovo che la mia vita sia più facile se faccio finta che la proprietà non esista. – hugomg

+0

Sto facendo fatica a chiuderlo come duplicato - tutti gli altri questini sono così prolissi ... – hugomg

+3

'c.prototype' è' b() 'e' b.prototype' è 'a()', quindi 'c.prototype' è' a() ' –

risposta

63

Va bene, giochiamo un po 'di gioco di mente:

dall'immagine qui sopra possiamo vedere:

  1. Quando creiamo una funzione come function Foo() {}, JavaScript crea un'istanza Function.
  2. Ogni istanza Function (la funzione di costruzione) ha una proprietà prototype che è un puntatore.
  3. La proprietà prototype della funzione di costruzione punta al suo oggetto prototipo.
  4. L'oggetto prototipo ha una proprietà constructor che è anche un puntatore.
  5. La proprietà constructor dell'oggetto prototipo fa riferimento alla sua funzione di costruzione.
  6. Quando creiamo una nuova istanza di come new Foo(), JavaScript crea un nuovo oggetto.
  7. La proprietà interna di [[proto]] dell'istanza punta al prototipo del costruttore.

Ora, la domanda sorge spontanea perché perché JavaScript non attribuisce la proprietà constructor all'oggetto istanza invece del prototipo. Considerate:

function defclass(prototype) { 
    var constructor = prototype.constructor; 
    constructor.prototype = prototype; 
    return constructor; 
} 

var Square = defclass({ 
    constructor: function (side) { 
     this.side = side; 
    }, 
    area: function() { 
     return this.side * this.side; 
    } 
}); 

var square = new Square(10); 

alert(square.area()); // 100 

Come si può vedere la proprietà constructor è solo un altro metodo del prototipo, come area nell'esempio di cui sopra. Ciò che rende speciale la proprietà constructor è che viene utilizzato per inizializzare un'istanza del prototipo. Altrimenti è esattamente la stessa di qualsiasi altro metodo del prototipo.

Definire la proprietà constructor sul prototipo è vantaggiosa per i seguenti motivi:

  1. E 'logicamente corretto. Ad esempio, considerare Object.prototype. La proprietà constructor di Object.prototype punta a Object. Se la proprietà constructor è stata definita sull'istanza, allora Object.prototype.constructor sarebbe undefined perché Object.prototype è un'istanza di null.
  2. Non viene trattato in modo diverso dagli altri metodi di prototipazione. Ciò semplifica il lavoro di new poiché non è necessario definire la proprietà constructor su ogni istanza.
  3. Ogni istanza condivide la stessa proprietà constructor. Quindi è efficiente.

Ora, quando si parla di eredità, abbiamo la seguente scenario:

dall'immagine qui sopra possiamo vedere:

  1. proprietà del costruttore derivato prototype è impostato su l'istanza del costruttore base.
  2. Quindi la proprietà interna [[proto]] dell'istanza del costruttore derivato punta anche a esso.
  3. Pertanto la proprietà constructor dell'istanza del costruttore derivata punta ora al costruttore base.

Per quanto riguarda il instanceof dell'operatore, contrariamente alla credenza popolare che non dipende dalla constructor proprietà dell'istanza. Come possiamo vedere dall'alto, ciò porterebbe a risultati errati.

L'operatore instanceof è un operatore binario (ha due operandi). Funziona su un oggetto istanza e una funzione costruttore. Come spiegare il Mozilla Developer Network, lo fa semplicemente il seguente:

function instanceOf(object, constructor) { 
    while (object != null) { 
     if (object == constructor.prototype) { //object is instanceof constructor 
      return true; 
     } else if (typeof object == 'xml') { //workaround for XML objects 
      return constructor.prototype == XML.prototype; 
     } 
     object = object.__proto__; //traverse the prototype chain 
    } 
    return false; //object is not instanceof constructor 
} 

Per dirla semplicemente, se Foo eredita da Bar, allora la catena di prototipi per l'istanza di Foo sarebbe:

  1. foo.__proto__ === Foo.prototype
  2. foo.__proto__.__proto__ === Bar.prototype
  3. foo.__proto__.__proto__.__proto__ === Object.prototype
  4. foo.__proto__.__proto__.__proto__.__proto__ === null

Come si può vedere, ogni oggetto eredita dal costruttore Object. La catena del prototipo termina quando una proprietà interna [[proto]] punta a null.

La funzione instanceof attraversa semplicemente la catena di prototipi dell'oggetto dell'istanza (il primo operando) e confronta il [[proto]] struttura interna di ciascun oggetto alla prototype proprietà della funzione di costruzione (il secondo operando). Se corrispondono, restituisce true; e se la catena del prototipo termina, restituisce false.

+4

vedi anche http://joost.zeekat.nl/constructors-considered-mildly-confusing.html – Christoph

+3

+1 Preferisco 'Object.getPrototypeOf' invece di' .__ proto__'. – pimvdb

+3

Ho usato solo la proprietà '__proto__' per motivi di spiegazione. Questo è il modo in cui è stato spiegato su Mozilla Developer Network. Tuttavia, la proprietà '__proto__' ha un vantaggio rispetto al metodo' Object.getPrototypeOf' in quanto è più veloce (nessuna funzione chiamata overhead) e che è implementata da tutti i principali browser. L'unica ragione per cui userò 'Object.getPrototypeOf' è di aggirare implementazioni come Rhino che non supportano la proprietà' __proto__'. Abbiamo tutti le nostre preferenze. Preferisco la proprietà '__proto__' perché è più leggibile. Saluti. =) –

12

Per default,

function b() {} 

poi b.prototype ha una proprietà .constructor che è impostato b automaticamente. Tuttavia, si sta sovrascrivendo il prototipo e scartando quindi tale variabile:

b.prototype = new a; 

Poi b.prototype non ha più una proprietà .constructor; è stato cancellato con la sovrascrittura. Lo appartiene allo ereditato da a e (new a).constructor === a e quindi (new b).constructor === a (si riferisce alla stessa proprietà nella catena del prototipo).

migliore da fare è semplicemente impostare manualmente in seguito:

b.prototype.constructor = b; 

Si potrebbe anche fare un po 'della funzione di questo:

function inherit(what, from) { 
    what.prototype = new from; 
    what.prototype.constructor = what; 
} 

http://jsfiddle.net/79xTg/5/

+1

È tutto vero. Ma allora non sapresti che un certo oggetto è * ereditato *. 'new b() instanceof a' restituisce' false' quando si usa '$ .extend' che potrebbe essere indesiderato nel caso dell'OP se ha bisogno di controllare l'ereditarietà. –

+0

@Robert Koritnik: buon punto, ma 'b instanceof b' è sempre falso poiché il costruttore non è un'istanza di se stesso. La funzione di estensione sembra funzionare per 'new b instanceof b', a meno che non mi stia sbagliando: http://jsfiddle.net/79xTg/3/. – pimvdb

+0

Ho modificato il mio commento. Ovviamente era un errore di battitura. E fammi modificare il tuo esempio: http://jsfiddle.net/79xTg/4/ '$ .extend' non fa ereditarietà ma piuttosto copia di' prototipo'. Quindi non puoi controllare l'ereditarietà usando questo. –

4

constructor è un regolare, non -è una proprietà numerica del valore predefinito della proprietà prototype di oggetti funzione. Pertanto, l'assegnazione a prototype perderà la proprietà.

instanceof continua a funzionare in quanto non utilizza constructor, ma piuttosto analizza la catena di prototipi dell'oggetto per il valore (corrente) della proprietà della funzione prototype, cioè foo instanceof Foo è equivalente a

var proto = Object.getPrototypeOf(foo); 
for(; proto !== null; proto = Object.getPrototypeOf(proto)) { 
    if(proto === Foo.prototype) 
     return true; 
} 
return false; 

In ECMAScript3 , non c'è modo di impostare una proprietà constructor che si comporta in modo identico a quella incorporata poiché le proprietà definite dall'utente sono sempre enumerabili (cioè visibili a for..in).

Modificato con ECMAScript5. Tuttavia, anche impostando manualmente constructor, il codice presenta ancora problemi: in particolare, è una cattiva idea impostare prototype su un'istanza di parent-'class '- il costruttore genitore non dovrebbe essere chiamato quando la classe' figlio ' 'è definito, ma piuttosto quando vengono create istanze figlio.

Ecco qualche esempio di codice ECMAScript5 per come dovrebbe essere fatto:

function Pet(name) { 
    this.name = name; 
} 

Pet.prototype.feed = function(food) { 
    return this.name + ' ate ' + food + '.'; 
}; 

function Cat(name) { 
    Pet.call(this, name); 
} 

Cat.prototype = Object.create(Pet.prototype, { 
    constructor : { 
     value : Cat, 
     writable : true, 
     enumerable : false, 
     configurable : true 
    } 
}); 

Cat.prototype.caress = function() { 
    return this.name + ' purrs.'; 
}; 

Se sei bloccato con ECMAScript3, avrete bisogno di utilizzare un costume clone() function invece di Object.create() e non sarà in grado di fanno constructor non enumerabile:

Cat.prototype = clone(Pet.prototype); 
Cat.prototype.constructor = Cat; 
Problemi correlati