Javascript Inheritance








We can use use the concept of prototypes to inherit properties and methods between two reference types.

Example

Implementing prototype chaining involves the following code pattern:


function Shape(){/* www. j  a  v  a2s .  c  o  m*/
   this.isDrawable = true;
}
Shape.prototype.getDrawable = function(){
   return this.isDrawable;
};

function Rectangle(){
   this.hasFourEdges = false;
}

//inherit from Shape
Rectangle.prototype = new Shape();

Rectangle.prototype.getFourEdges = function (){
   return this.hasFourEdges;
};

var instance = new Rectangle();
console.log(instance.getDrawable());   //true

This code defines two types: Shape and Rectangle.

Rectangle inherits from Shape by creating a new instance of Shape and assigning it to Rectangle.prototype.

This overwrites the original prototype and replaces it with a new object, which means that all properties and methods that typically exist on an instance of Shape now also exist on Rectangle.prototype.

After the inheritance takes place, a method is assigned to Rectangle.prototype, adding a new method on top of what was inherited from Shape.

instance points to Rectangle.prototype, and Rectangle.prototype points to Shape.prototype.

The code above generates the following result.





Prototype and Instance Relationships


function Shape(){/*from ww  w.ja  va2 s.c  o  m*/
   this.isDrawable = true;
}
Shape.prototype.getDrawable = function(){
   return this.isDrawable;
};

function Rectangle(){
   this.hasFourEdges = false;
}

//inherit from Shape
Rectangle.prototype = new Shape();

Rectangle.prototype.getFourEdges = function (){
   return this.hasFourEdges;
};

var instance = new Rectangle();

console.log(instance instanceof Object);      //true
console.log(instance instanceof Shape);   //true
console.log(instance instanceof Rectangle);     //true

The code above generates the following result.





isPrototypeOf

We can also use the isPrototypeOf() method.

Each prototype in the chain has access to this method, which returns true for an instance in the chain, as in this example:


function Shape(){//from   www  .  java2 s.c o  m
   this.isDrawable = true;
}
Shape.prototype.getDrawable = function(){
   return this.isDrawable;
};

function Rectangle(){
   this.hasFourEdges = false;
}

//inherit from Shape
Rectangle.prototype = new Shape();

Rectangle.prototype.getFourEdges = function (){
   return this.hasFourEdges;
};

var instance = new Rectangle();

console.log(Object.prototype.isPrototypeOf(instance));    //true
console.log(Shape.prototype.isPrototypeOf(instance)); //true
console.log(Rectangle.prototype.isPrototypeOf(instance));   //true

The code above generates the following result.

Methods

Often a Rectangle will need to either override a Shape method or introduce new methods that don't exist on the Shape.

To accomplish this, the methods must be added to the prototype after the prototype has been assigned. Consider this example:


function Shape(){//  w w  w. j a v  a2 s .  c  o m
    this.isDrawable = true;
}
Shape.prototype.getDrawable = function(){
    return this.isDrawable;
};
function Rectangle(){
    this.hasFourEdges = false;
}
//inherit from Shape
Rectangle.prototype = new Shape();

//new method
Rectangle.prototype.getFourEdges = function (){
    return this.hasFourEdges;
};
//override existing method
Rectangle.prototype.getDrawable = function (){
    return false;
};
var instance = new Rectangle();
console.log(instance.getDrawable());   //false

The code above generates the following result.

Problems with Prototype Chaining

prototype properties containing reference values are shared with all instances; this is why properties are typically defined within the constructor instead of on the prototype.

When implementing inheritance using prototypes, the prototype actually becomes an instance of another type. What once were instance properties become prototype properties.


function Shape(){// w w w  .  jav  a 2s .c om
    this.colors = ["red", "blue", "green"];
}
function Rectangle(){
}

//inherit from Shape
Rectangle.prototype = new Shape();

var instance1 = new Rectangle();
instance1.colors.push("black");
console.log(instance1.colors);    //"red,blue,green,black"

var instance2 = new Rectangle();
console.log(instance2.colors);    //"red,blue,green,black"

A second issue with prototype chaining is that you cannot pass arguments into the Shape constructor when the Rectangle instance is being created.

The code above generates the following result.

Constructor Stealing

Constructor Stealing is to call the Shape constructor from within the Rectangle constructor.

The apply() and call() methods can be used to execute a constructor on the newly created object.


function Shape(){//  w  w  w  . j ava  2  s  .  c  om
    this.colors = ["red", "blue", "green"];
}

function Rectangle(){
    //inherit from Shape
    Shape.call(this);
}

var instance1 = new Rectangle();
instance1.colors.push("black");
console.log(instance1.colors);    //"red,blue,green,black"

var instance2 = new Rectangle();
console.log(instance2.colors);    //"red,blue,green"

The code above generates the following result.

Passing Arguments

One advantage that constructor stealing offers over prototype chaining is the ability to pass arguments into the Shape constructor from within the Rectangle constructor.


function Shape(name){/*from  w w w. j a  va 2 s.co  m*/
    this.name = name;
}
function Rectangle(){
   //inherit from Shape passing in an argument
   Shape.call(this, "Nicholas");

   //instance isDrawable
   this.age = 29;
}
var instance = new Rectangle();
console.log(instance.name);    //"Nicholas";
console.log(instance.age);     //29

The code above generates the following result.

Problems with Constructor Stealing

Methods must be defined inside the constructor, so there's no function reuse.

Methods defined on the Shape's prototype are not accessible on the Rectangle.

Combination Inheritance

Combination inheritance combines prototype chaining and constructor stealing to get the best of each approach.

Combination inheritance uses prototype chaining to inherit properties/methods on the prototype and uses constructor stealing to inherit instance properties.

This allows function reuse by defining methods on the prototype and allows each instance to have its own properties. Consider the following:


function Shape(name){/*w ww  . jav  a  2 s . c o m*/
   this.name = name;
   this.colors = ["red", "blue", "green"];
}
Shape.prototype.sayName = function(){
   console.log(this.name);
};
function Rectangle(name, width){
   //inherit properties
   Shape.call(this, name);
   this.width = width;
}
//inherit methods
Rectangle.prototype = new Shape();

Rectangle.prototype.printWidth = function(){
   console.log(this.width);
};
var instance1 = new Rectangle("Rect1", 10);
instance1.colors.push("black");
console.log(instance1.colors);
instance1.sayName();
instance1.printWidth();

var instance2 = new Rectangle("Rect2", 2);
console.log(instance2.colors);
instance2.sayName();
instance2.printWidth();

The code above generates the following result.