Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScript Inheritance with Prototypes -- 'constructor' property?

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?

like image 242
ChaseMoskal Avatar asked Mar 11 '14 03:03

ChaseMoskal


People also ask

How you inherit properties in JS prototypes?

When it comes to inheritance, JavaScript only has one construct: objects. Each object has a private property which holds a link to another object called its prototype. That prototype object has a prototype of its own, and so on until an object is reached with null as its prototype.

Which prototype property is a prototype object that is used to implement inheritance and shared properties?

In programming, this is called “prototypal inheritance”. And soon we'll study many examples of such inheritance, as well as cooler language features built upon it. The property [[Prototype]] is internal and hidden, but there are many ways to set it.

Are constructors inherited in JavaScript?

Conclusion. The constructor property is a piece of the inheritance mechanism in JavaScript.

Is a constructor a prototype in JavaScript?

Using a constructorIn JavaScript, all functions have a property named prototype . When you call a function as a constructor, this property is set as the prototype of the newly constructed object (by convention, in the property named __proto__ ).


3 Answers

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
like image 198
thefourtheye Avatar answered Oct 01 '22 17:10

thefourtheye


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;

like image 29
helly0d Avatar answered Oct 01 '22 17:10

helly0d


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:

/** 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 };



Advanced Methodology:

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



Both methodologies above yield the same test results:

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 :)

like image 3
ChaseMoskal Avatar answered Oct 01 '22 16:10

ChaseMoskal