2011-11-11 11 views
35

Tenga en cuenta el siguiente código.Herencia de JavaScript y la propiedad del constructor

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() 
  • ¿Por qué no es el constructor actualizado para B y C?
  • ¿Estoy haciendo mal la herencia?
  • ¿Cuál es la mejor manera de actualizar el constructor?

Además, tenga en cuenta lo siguiente.

console.log(new a() instanceof a); //true 
console.log(new b() instanceof b); //true 
console.log(new c() instanceof c); //true 
  • Dado que (new c()).constructor es igual a a() y Object.getPrototypeOf(new c())a{ } es, ¿cómo es posible que instanceof saber que new c() es una instancia de c?

http://jsfiddle.net/ezZr5/

+3

¿Alguna razón por la que necesita que se actualice el constructor? Encuentro que mi vida es más fácil si solo pretendo que la propiedad no existe. – hugomg

+0

Me está resultando difícil cerrar esto como un duplicado - todos los otros questinos son tan detallados ... – hugomg

+3

'c.prototype' es' b() 'y' b.prototype' es 'a()', por lo tanto 'c.prototype' es' a() ' –

Respuesta

63

Está bien, vamos a jugar un poco de juego de la mente:

Partiendo de esta imagen podemos ver:

  1. Cuando creamos una función como function Foo() {}, JavaScript crea una instancia Function.
  2. Cada instancia de Function (la función constructora) tiene una propiedad prototype que es un puntero.
  3. La propiedad prototype de la función constructora apunta a su objeto prototipo.
  4. El objeto prototipo tiene una propiedad constructor que también es un puntero.
  5. La propiedad constructor del objeto prototipo apunta a su función constructora.
  6. Cuando creamos una nueva instancia de Foo como new Foo(), JavaScript crea un nuevo objeto.
  7. La propiedad interna [[proto]] de la instancia apunta al prototipo del constructor.

Ahora, surge la pregunta de por qué JavaScript no asocia la propiedad constructor al objeto de instancia en lugar del prototipo. Considere:

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 

Como se puede ver la propiedad constructor es sólo otro método del prototipo, como area en el ejemplo anterior. Lo que hace especial a la propiedad constructor es que se usa para inicializar una instancia del prototipo. De lo contrario, es exactamente lo mismo que cualquier otro método del prototipo.

Definición de la propiedad constructor en el prototipo es ventajoso por las siguientes razones:

  1. Es lógicamente correcto. Por ejemplo, considere Object.prototype. La propiedad constructor de Object.prototype apunta a Object. Si la propiedad constructor se definió en la instancia, entonces Object.prototype.constructor sería undefined porque Object.prototype es una instancia de null.
  2. No se trata de manera diferente a otros métodos de prototipos. Esto facilita el trabajo de new ya que no necesita definir la propiedad constructor en cada instancia.
  3. Cada instancia comparte la misma propiedad constructor. Por lo tanto, es eficiente.

Ahora bien, cuando hablamos de herencia, tenemos el siguiente escenario:

Partiendo de esta imagen podemos ver:

  1. propiedad del constructor derivado prototype se establece en la instancia del constructor base.
  2. Por lo tanto, la propiedad [[proto]] interna de la instancia del constructor derivado también lo señala.
  3. Por lo tanto, la propiedad constructor de la instancia del constructor derivado apunta ahora al constructor base.

En cuanto a la instanceof operador, contrariamente a la creencia popular que no depende de la propiedad constructor de la instancia. Como podemos ver desde arriba, eso llevaría a resultados erróneos. El operador instanceof es un operador binario (tiene dos operandos). Opera en un objeto de instancia y una función de constructor. Como se explicará en Mozilla Developer Network, simplemente hace lo siguiente:

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 
} 

En pocas palabras, si Foo hereda de Bar, a continuación, la cadena de prototipo para la instancia de Foo sería:

  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

Como puede ver, cada objeto hereda del constructor Object. La cadena del prototipo finaliza cuando una propiedad interna [[proto]] apunta a null.

La función instanceof simplemente atraviesa la cadena de prototipo del objeto de instancia (el primer operando) y compara la [[proto]] propiedad interna de cada objeto a la propiedad prototype de la función de constructor (el segundo operando). Si coinciden, devuelve true; y si la cadena del prototipo termina, devuelve false.

+4

ver también http://joost.zeekat.nl/constructors-considered-mildly-confusing.html – Christoph

+3

+1 Preferiría 'Object.getPrototypeOf' en lugar de' .__ proto__' embargo. – pimvdb

+3

Solo utilicé la propiedad '__proto__' por razones de explicación. Esa es la forma en que se explicó en Mozilla Developer Network. Sin embargo, la propiedad '__proto__' tiene una ventaja sobre el método' Object.getPrototypeOf' en que es más rápida (sin sobrecarga de llamada de función) y que es implementada por todos los principales navegadores. La única razón por la que usaría 'Object.getPrototypeOf' es para evitar implementaciones como Rhino que no admiten la propiedad' __proto__'. Todos tenemos nuestras propias preferencias. Prefiero la propiedad '__proto__' porque es más legible. Aclamaciones. =) –

12

Por defecto,

function b() {} 

continuación b.prototype tiene una propiedad .constructor que se fija para b automáticamente. Sin embargo, actualmente está sobrescribiendo el prototipo y descartando por tanto, esa variable:

b.prototype = new a; 

Entonces b.prototype no tiene una propiedad .constructor más; fue borrado con la sobrescritura. Es hereda de a y (new a).constructor === a, y por lo tanto (new b).constructor === a (se refiere a la misma propiedad en la cadena del prototipo).

Mejor que hacer es simplemente establecer manualmente después:

b.prototype.constructor = b; 

También podría hacer una pequeña función para esto:

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

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

+1

Eso es todo cierto. Pero entonces no sabrías que cierto objeto es * heredado *. 'new b() instanceof a' devuelve' false' cuando usa '$ .extend' que puede no ser deseado en el caso de OP si necesita verificar la herencia. –

+0

@Robert Koritnik: Buen punto, pero 'b instanceof b' siempre es falso, ya que el constructor no es una instancia de sí mismo. La función extender parece funcionar para 'new b instanceof b', a menos que te malinterprete: http://jsfiddle.net/79xTg/3/. – pimvdb

+0

Edité mi comentario. Por supuesto, fue un error tipográfico. Y permítanme editar su ejemplo: http://jsfiddle.net/79xTg/4/ '$ .extend' no hace herencia, sino copia' prototype'. Por lo tanto, no puedes verificar la herencia usándolo. –

4

constructor es un habitual, no -una propiedad en gran cantidad del valor predeterminado de la propiedad prototype de los objetos de función. Por lo tanto, la asignación a prototype perderá la propiedad.

instanceof habrá todavía trabajar, ya que no utiliza constructor, sino más bien escanea la cadena de prototipo del objeto para el valor (actual) de la propiedad de la función prototype, es decir foo instanceof Foo es equivalente a

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

En ECMAScript3 , no hay forma de establecer una propiedad constructor que se comporte de manera idéntica a la incorporada ya que las propiedades definidas por el usuario siempre son enumerables (es decir, visibles a for..in).

Esto cambió con ECMAScript5. Sin embargo, incluso si establece constructor manualmente, su código aún tiene problemas: en particular, es una mala idea establecer prototype en una instancia del padre-clase '; el constructor padre no debe invocarse cuando la clase-hijo 'está definido, pero más bien cuando se crean instancias hijo.

Aquí hay un código de ejemplo para ECMAScript5 cómo debe hacerse:

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.'; 
}; 

Si le pegan con ECMAScript3, necesitará usar una costumbre en lugar de clone() functionObject.create() y no será capaz de hacer constructor no numerable:

Cat.prototype = clone(Pet.prototype); 
Cat.prototype.constructor = Cat; 
Cuestiones relacionadas