JavaScript does not offer classical inheritance structure where child class inherit properties of parent classes. JavaScript allows inheritance between objects with the help of prototype.
JavaScript built-in approach for inheritance is called prototype chaining, or prototypal inheritance. Prototype properties are automatically available on object instances which is a form of ineritance. Because prototype is also an object, it has its own properties and inherits properties from that. This is the prototype chain. An object inherits from its prototype, while that prototype in turn inherits from its prototype, and so on.
All objects inherit from Object. More specifically, all objects inherit from Object.prototype.
var person = {
name: "shahzad"
};
var prototype = Object.getPrototypeOf(person);
console.log(prototype === Object.prototype); //returns true
There are five method that appear on all objects through inheritance. They are inherited from Object.prototype;
hasOwnProperty() -> Determines whether an own property with the given name exists
propertyIsEnumerable() -> Determines whether an own property is enumerable
isPrototypeOf() -> Determines whether the object is the prototype of another
valueOf() -> Returns the value representation of the object
toString() -> Returns a string representation of the object
The last two are important when we need to make objects work consistently in JavaScript, and sometime we might want to define them ouself.
valueOf()
This method gets called whenever an operator is used on an object. By default, valueOf() simply returns the object instance. The primitive wrapper types override valueOf() so that it returns and string for String, a Boolean for Boolean and so on.
var now = new Date();
var earlier = new Date(2020,1,1);
console.log(now > earlier) //returns true
now is the date representing the current time, and earlier is a fixed date in the past. When the greater-than-operator (>) is used, the valueOf() method is called on both objects before the comparison is performed. We can even subtract one date from another and get the difference in epoch time because of valueOf().
We can define our own valueOf() method if our objects are intended to be used with operators. We are not chaning how the operator works, only what value is used with operator’s default behavior.
toString()
The toString() method is called as a fallback whenever valueOf() returns a reference value instead of primitive value. It is also implicitly called on primitive values whenever JavaScript is expecting a string. For example, when a string is used as one operand for the plus operator, the other operand is automatically converted to a string. If the other operand is a primitive value, it is converted into a string representations (for example, true becomes “true”), but if its is reference value, then valueOf() is called.
var person = {
name: "shahzad"
};
var message = "Name = " + person;
console.log(message); //returns Name = [object Object]
The code constructs the string by combining “Name =” with person. Since person is an object, its toString() method is called. The method is inherited from Object.prototype and returns the default value of “[object object]” in most JavaScript engines. If we are not happy with this value, we need to provide our own toString() method implementation.
var person = {
name: "shahzad",
toString: function() {
return "[Name " + this.name + " ]"
}
};
var message = "Name = " + person;
console.log(message); //returns Name = [Name shahzad ]
Object Inheritance
The simples type of inheritance is between objects. All we have to do is specify what should be the new object’s [[Prototype]].
When a property is accessed on an object, the JavaScript engine goes through a search process. If the property is found on the instance, that property value is used. If the property is not found on the instance, the search continues on [[Prototype]]. If the property is still not found, the search continues to that object’s [[Prototype]], and so on until the end of the chain is reached. That chain usually ends with Object.prototype, whose [[Prototype]] is set to null.
We can also create objects with a null [[Prototype]] via Object.create();
var nakedObject = Object.create(null);
console.log("toString" in nakedObject); //returns false
console.log("valueOf" in nakedObject); //returns false
The naked object is an object with no prototype chain. That means built-in methods such as toString() and valueOf() aren’t present on the object. This object is a completely blank slate with no predefined properties, which makes it perfect for creating a lookup hash without potential naming collisions with inherited property names. There aren’t many other uses for an object like this. For example, any time you use an operator on this object, you’ll get an error “Cannot convert object to primitive value.”.
Constructor Inheritance
Object inheritance is the basis of constructor inheritance. Every function has a prototype property that can be modified or replaced. The property is automatically assigned to be a new generic object and has single own property called constructor. In effect, the JavaScript engine does the following for you;
//your write this
function MyConstructor(){
//initialization
}
//JavaScript engine does this for you behind the scenes
MyConstructor.prototype =
Object.create(Object.prototype, {
constructor: {
configurable: true,
enumerable: true,
value: MyConstructor,
writable: true
}
});
MyConstructor is a subtype of Object, and Object is a supertype of MyConstructor. Prototype property is writable, we can change the prototype chain by overwriting it.
//first constructor
function Rectangle(length, width){
this.length = length;
this.width = width;
};
Rectangle.prototype.getArea = function() {
return this.length * this.width;
};
Rectangle.prototype.toString = function() {
return "[Rectangle " + this.length + " X " + this.width + "]";
};
//inherits from Rectangle
//second constructor
function Square(size) {
this.length = size;
this.width = size;
};
Square.prototype = new Rectangle();
Square.prototype.constructor = Square;
Square.prototype.toString = function() {
return "[Square " + this.length + " x " + this.width + "]";
};
var rect = new Rectangle(5,10);
var square = new Square(6);
console.log(rect.getArea()); //returns 50
console.log(square.getArea()); //returns 36
console.log(rect.toString()); //returns [Rectangle 5 X 10]
console.log(square.toString()); //returns [Square 6 x 6]
console.log(rect instanceof Rectangle); //returns true
console.log(rect instanceof Object); //returns true
console.log(square instanceof Square); //returns true
console.log(square instanceof Rectangle); //returns true
console.log(square instanceof Object); //returns true
In the above code, there are two constructors: Rectangle and Square. The Square constructor has it prototype property overwritten with an instance of Rectangle. No arguments are passed into Rectangle at this point because they don’t need to be used, and if they were, all instances of Square would share the same dimensions. The constructor property is restored on Square.prototype after the original value is overwritten.
rect is created as an instance of Rectangle, and square is created an an instance of Square. Both objects have the getArea() method because it is inherited from Rectangle.prototype. The square variable is considered as instance of Square as well as Rectangle and Object because instanceof uses the prototype chain to determine the object type.
Square.prototype doesn’t actually need to be overwritten. Rectangle constructor isn’t doing anything that is necessary for Square. The only relevant part is that Square.prototype needs to somehow link to Rectangle.prototype in order for inheritance to happen. That means, we can simplify the example by using Object.create();
//inherits from Rectangle
function Square(size) {
this.length = size;
this.width = size;
};
Square.prototype = Object.create(Rectangle.prototype, {
constructor: {
configurable: true,
enumerable: true,
value: Square,
writable: true
}
});
Square.prototype.toString = function() {
return "[Square " + this.length + " x " + this.width + "]";
};
var square = new Square(5)
console.log(square.getArea()); //returns 25
console.log(square instanceof Square); //returns true
console.log(square instanceof Rectangle); //returns true
console.log(square instanceof Object); //returns true
console.log(square.toString()); //returns [Square 5 x 5]
In this version, Square.prototype is overwritten with a new object that inherits from Rectangle.prototype, and the Rectangle constructor is never called. That means we don’t need to worry about causing an error by calling the constructor without arguments anymore.
Always make sure that you overwrite the prototype before adding properties to it, or you will lose the added methods when the overwrite happens.
Accessing Supertype (Base) Methods
In our previous example, the Square type has its own toString() method that shadown toString() on the prototype. What if we want to use base type method? JavaScript allows you to directly access the method on the supertype’s prototype and use either call() or apply() to execute the method.
Replace this;
Square.prototype.toString = function() {
return "[Square " + this.length + " x " + this.width + "]";
};
With this;
Square.prototype.toString = function() {
var text = Rectangle.prototype.toString.call(this);
return text.replace("Rectangle", "Square");
};
var square = new Square(5)
console.log(square.getArea()); //returns 25
console.log(square.toString()); //returns [Square 5 X 5]