I\'ve seen a lot of stuff like this, and am looking for the proper solution to basic JavaScript inheritance:
function Food(){} // Food constructor (class)
Your only problem in your logic was setting the same object basicFood
to both Bread.prototype
and Sushi.prototype
. Try to do something like this:
Bread.prototype = new Food();
Bread.prototype.constructor = Bread;
Sushi.prototype = new Food();
Sushi.prototype.constructor = Sushi;
Now the instanceof
bread
and sushi
will be Food
but the constructors will be Bread
and Sushi
for each of them in particular;
Lets examine your code a little bit.
function Food(){}
function Bread(){}
function Sushi(){}
var basicFood = new Food();
Bread.prototype = basicFood;
Sushi.prototype = basicFood;
Note: When you set the same object as the prototype of two objects, augmentation in one prototype, will reflect in the other prototype as well. For example,
Bread.prototype = basicFood;
Sushi.prototype = basicFood;
Bread.prototype.testFunction = function() {
return true;
}
console.log(Sushi.prototype.testFunction()); // true
Lets get back to your questions.
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?
As per the instanceof docs from MDN,
The instanceof operator tests whether an object has in its prototype chain the prototype property of a constructor.
So when we do something like
object1 instanceof object2
JavaScript will try to find if the prototype of the object2
is in the prototype chain of object1
.
In this case, it will return true
only when the Bread.prototype
is in the prototype chain of sushi
. We know that sushi
is constructed from Sushi
. So, it will take Sushi
's prototype and check if it is equal to Bread
's prototype. Since, they both point to the same basicFood
object, that returns true
. Same case for, bread instanceof Sushi
as well.
So, the right way to inherit would be, like this
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
This is my personal solution, which I have developed from the combined wisdom nuggets of @thefourtheye, @FelixKling, @SeanKinsey, and even the antics of @helly0d:
/** 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 };
It's better because it makes the constructor
prototype property a non-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 };
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
A very special thanks to @FelixKling, whose experience helped hone my understanding in the chat outside of this thread -- also to @thefourtheye, who was the first to show me the correct way -- and also to @SeanKinsey, who highlighted the usefulness of being able to run the parent constructor within the context of the children.
I community wiki'd this answer -- please let me know or edit yourself if you find anything in this answer's code which is suspect :)
What you are doing wrong is to reuse the basicFood
object for multiple child 'classes'. Instead, new up a new one. That way, as you add members to the prototype (new instance of the parent), you're adding it to an instance that is not shared among other inheriting classes.
Now, there's one thing that your code is lacking, and that is constructors without side effects. Many constructors requires arguments, and will throw without them - but how can you construct a prototype for a new descending class without new'ing up a parent? Well, we're not actually interested in the parent function, only in the parents prototype. So what you can do is
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