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)
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.
Who is constructor
supposed to really be?
And does constructor
have anything to do with the results of instanceof
?
I find myself wondering, what is correct? I understand that many would choose to give each food class (Bread, Sushi, etc) a new instance of Food, rather than giving them all the same basicFood instance.. I want this more optimal solution (not making unneeded instances).
Given our Food, Bread, Sushi, and basicFood:
function Food(){}
function Bread(){}
function Sushi(){}
var basicFood = new Food();
I figured I could create an instancing helper, which would define a non-enumerable non-writable non-configurable property 'constructor' on the new instance:
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);
In testing this, I realized instanceof
is not behaving the way I thought it might:
// 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?
Looking into it more, I can't seem to get instanceof
to work the way I'd assume at all:
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?
I want bread
and sushi
to both be instances of Food -- not each other.
How can I achieve JavaScript inheritance while maintaining the expected behavior for the constructor
property as well as the instanceof
operator?
This is my personal solution, which I have developed from the combined wisdom nuggets of @thefourtheye, @FelixKling, @SeanKinsey, and even the antics of @helly0d:
Simplest Solution:
Advanced Methodology:
It's better because it makes the
constructor
prototype property a non-enumerable.Both methodologies above yield the same test results:
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
Lets examine your code a little bit.
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,
Lets get back to your questions.
As per the
instanceof
docs from MDN,So when we do something like
JavaScript will try to find if the prototype of the
object2
is in the prototype chain ofobject1
.In this case, it will return
true
only when theBread.prototype
is in the prototype chain ofsushi
. We know thatsushi
is constructed fromSushi
. So, it will takeSushi
's prototype and check if it is equal toBread
's prototype. Since, they both point to the samebasicFood
object, that returnstrue
. Same case for,bread instanceof Sushi
as well.So, the right way to inherit would be, like this
Your only problem in your logic was setting the same object
basicFood
to bothBread.prototype
andSushi.prototype
. Try to do something like this:Now the
instanceof
bread
andsushi
will beFood
but the constructors will beBread
andSushi
for each of them in particular;