clases - ¿Herencia de JavaScript con prototipos-propiedad ''constructor''?
javascript prototype constructor (4)
Esta es mi solución personal, que he desarrollado a partir de los nuggets de sabiduría combinados de @thefourtheye, @FelixKling, @SeanKinsey e incluso las travesuras de @ helly0d:
La solución más simple:
/** Food Class -- You can bite all foods **/
function Food(){ this.bites = 0 };
Food.prototype.bite = function(){ console.log("Yum!"); return this.bites += 1 };
/** All Foods inherit from basicFood **/
var basicFood = new Food();
/** Bread inherits from basicFood, and can go stale **/
function Bread(){
Food.apply(this); // running food''s constructor (defines bites)
this.stale = false;
};
Bread.prototype = Object.create( basicFood );
Bread.prototype.constructor = Bread; // just conventional
Bread.prototype.goStale = function(){ return this.stale = true };
/** Sushi inherits from basicFood, and can be cooked **/
function Sushi(){
Food.apply(this);
this.raw = true;
};
Sushi.prototype = Object.create( basicFood );
Sushi.prototype.constructor = Sushi;
Sushi.prototype.cook = function(){ return this.raw = false };
Metodología avanzada:
Es mejor porque hace que la propiedad prototipo del constructor
no sea enumerable.
/** My handy-dandy extend().to() function **/
function extend(source){
return {to:function(Constructor){
Constructor.prototype = Object.create(source);
Object.defineProperty(Constructor.prototype, ''constructor'', {
enumerable: false,
configurable: false,
writable: false,
value: Constructor
});
return Constructor;
}}
};
function Food(){ this.bites = 0 };
Food.prototype.bite = function(){ console.log("Yum!"); return this.bites += 1 };
var basicFood = new Food();
var Bread = extend(basicFood).to(function Bread(){
Food.apply(this);
this.stale = false;
});
Bread.prototype.goStale = function(){ return this.stale = true };
var Sushi = extend(basicFood).to(function Sushi(){
Food.apply(this);
this.raw = true;
});
Sushi.prototype.cook = function(){ return this.raw = false };
Las dos metodologías anteriores dan los mismos resultados de prueba:
var food = new Food();
var bread = new Bread();
var sushi = new Sushi();
console.log( food instanceof Food ); // true
console.log( food instanceof Bread ); // false
console.log( food instanceof Sushi ); // false
console.log( bread instanceof Food ); // true
console.log( bread instanceof Bread ); // true
console.log( bread instanceof Sushi ); // false
console.log( sushi instanceof Food ); // true
console.log( sushi instanceof Bread ); // false
console.log( sushi instanceof Sushi ); // true
console.log( food.constructor ); // Food
console.log( bread.constructor ); // Bread
console.log( sushi.constructor ); // Sushi
Un agradecimiento muy especial a @FelixKling, cuya experiencia me ayudó a comprender mejor el chat fuera de este hilo, también a @thefourtheye, quien fue el primero en mostrarme la forma correcta, y también a @SeanKinsey, quien destacó la utilidad de poder ejecutar el constructor padre en el contexto de los hijos.
Esta comunidad ha respondido esta respuesta. Por favor, avíseme o edíteme si encuentra algo en el código de esta respuesta que sea sospechoso :)
He visto muchas cosas como esta y estoy buscando la solución adecuada para la herencia básica de JavaScript:
function Food(){} // Food constructor (class)
function Bread(){} // Bread constructor (class)
var basicFood = new Food(); // Food classes will inherit from this basicFood instance.
Bread.prototype = basicFood; // Bread now inherits from Food.
var bread = new Bread(); // We create some bread!
bread.constructor == Food; // Though, now we feel very uneasy about how
// the constructor is wrong,
Bread.prototype.constructor = Bread; // So we explicitly set the prototype''s constructor
bread = new Bread(); // and when we create our new bread,
bread.constructor == Bread; // we feel much better as the constructor appears correct.
// The issue? Suppose we have another food item,
// like in a real inheritance situation:
function Sushi(){}; // We might be
Sushi.prototype = basicFood; // tempted to do
Sushi.prototype.constructor = Sushi; // the same thing
var sushi = new Sushi(); // again
sushi.constructor == Sushi; // Before we realize
bread.constructor == Sushi; // that we''ve ruined our bread.
basicFood.constructor != Food; // More importantly, we''ve really ruined all our basicFood,
// because while it''s a prototype,
// it''s also an object in its own right,
// and deserves an accurate constructor property.
¿Quién se supone que es realmente el constructor
?
¿Y el constructor
tiene algo que ver con los resultados de instanceof
?
Me pregunto, ¿qué es lo correcto? Entiendo que muchos optarían por darle a cada clase de alimentos (pan, sushi, etc.) una nueva instancia de Alimentos, en lugar de darles a todos la misma instancia de BasicFood. Quiero esta solución más óptima (no hacer instancias innecesarias).
Teniendo en cuenta nuestra comida, pan, sushi y alimentos básicos:
function Food(){}
function Bread(){}
function Sushi(){}
var basicFood = new Food();
Pensé que podría crear un ayudante de creación de instancias, que definiría un "constructor" de propiedad no configurable y no enumerable no enumerable en la nueva instancia:
Bread.prototype = basicFood; // We still simply inherit from basicFood
Sushi.prototype = basicFood;
// But we use this helper function when we make instances
function reconstructify(target, Constructor){
return Object.defineProperty(target, ''constructor'', {
enumerable: false,
configurable: false,
writable: false,
value: Constructor
});
}
var bread = reconstructify(new Bread(), Bread); // Like so
var sushi = reconstructify(new Sushi(), Sushi);
Al probar esto, me di cuenta de que instanceof
no se está comportando de la manera que pensé:
// True expressions for testing -- all good
basicFood.constructor == Food;
bread.constructor == Bread;
sushi.constructor == Sushi;
basicFood instanceof Food; // good also
bread instanceof Food;
sushi instanceof Food;
sushi instanceof Bread; // uh oh, not so good that this is true
bread instanceof Sushi; // why is this?
Si lo analizo más, parece que no puedo ponerme a trabajar de la manera que supongo:
function Food(){}
function Bread(){}
function Sushi(){}
var basicFood = new Food();
Bread.prototype = basicFood;
Sushi.prototype = basicFood;
var bread = new Bread();
var sushi = new Sushi();
sushi instanceof Bread; // why true?
bread instanceof Sushi; // why true?
Quiero que el bread
y el sushi
sean ejemplos de comida, no uno del otro.
¿Cómo puedo obtener la herencia de JavaScript al mismo tiempo que mantengo el comportamiento esperado para la propiedad del constructor
, así como para el operador instanceof
?
Lo que está haciendo mal es reutilizar el objeto basicFood
para varias "clases" secundarias. En su lugar, una nueva nueva. De esa manera, a medida que agrega miembros al prototipo (nueva instancia del padre), lo agrega a una instancia que no se comparte entre otras clases hereditarias.
Ahora, hay una cosa que le falta a su código, y es a los constructores sin efectos secundarios. Muchos constructores requieren argumentos y se lanzarán sin ellos, pero ¿cómo se puede construir un prototipo para una nueva clase descendente sin crear un nuevo padre? Bueno, en realidad no estamos interesados en la función principal, solo en el prototipo de los padres. Entonces lo que puedes hacer es
function Parent() { /*some side effect or invariant */ }
Parent.prototype.foo = ...
function Child() { Parent.call(this); }
// the next few lines typically go into a utility function
function F() {} // a throw-away constructor
F.prototype = Parent.prototype; // borrow the real parent prototype
F.prototype.constructor = Parent; // yep, we''re faking it
Child.prototype = new F(); // no side effects, but we have a valid prototype chain
Child.prototype.bar = ... // now continue adding to the new prototype
Su único problema en su lógica fue establecer el mismo objeto basicFood
tanto para Bread.prototype
como para Sushi.prototype
. Intenta hacer algo como esto:
Bread.prototype = new Food();
Bread.prototype.constructor = Bread;
Sushi.prototype = new Food();
Sushi.prototype.constructor = Sushi;
Ahora la instanceof
bread
y el sushi
será la Food
pero los constructores serán el Bread
y el Sushi
para cada uno en particular;
Vamos a examinar tu código un poco.
function Food(){}
function Bread(){}
function Sushi(){}
var basicFood = new Food();
Bread.prototype = basicFood;
Sushi.prototype = basicFood;
Nota: Cuando configura el mismo objeto que el prototipo de dos objetos, el aumento en un prototipo también se reflejará en el otro prototipo. Por ejemplo,
Bread.prototype = basicFood;
Sushi.prototype = basicFood;
Bread.prototype.testFunction = function() {
return true;
}
console.log(Sushi.prototype.testFunction()); // true
Regresemos a sus preguntas.
var bread = reconstructify(new Bread(), Bread);
var sushi = reconstructify(new Sushi(), Sushi);
console.log(sushi instanceof Bread); // Why true?
console.log(bread instanceof Sushi); // Why true?
Según la instanceof
documentos de MDN ,
El operador instanceof prueba si un objeto tiene en su cadena de prototipo la propiedad prototipo de un constructor.
Entonces cuando hacemos algo como
object1 instanceof object2
JavaScript intentará encontrar si el prototipo del object2
está en la cadena de prototipo del object1
.
En este caso, se devolverá true
solo cuando el Bread.prototype
encuentre en la cadena de prototipos de sushi
. Sabemos que el sushi
está hecho de Sushi
. Entonces, tomará el prototipo de Sushi
y verificará si es igual al prototipo de Bread
. Desde entonces, ambos apuntan al mismo objeto de basicFood
, que devuelve true
. El mismo caso para el bread instanceof Sushi
también.
Entonces, la forma correcta de heredar sería, así
function Food() {}
function Bread() {}
function Sushi() {}
Bread.prototype = Object.create(Food.prototype);
Bread.prototype.constructor = Bread;
Sushi.prototype = Object.create(Food.prototype);
Sushi.prototype.constructor = Sushi;
var bread = new Bread();
var sushi = new Sushi();
console.log(sushi instanceof Bread); // false
console.log(bread instanceof Sushi); // false
console.log(sushi.constructor); // [Function: Sushi]
console.log(bread.constructor); // [Function: Bread]
console.log(sushi instanceof Food); // true
console.log(bread instanceof Food); // true
console.log(sushi instanceof Sushi); // true
console.log(bread instanceof Bread); // true