How Inheritance is achieved using Prototype in Javascript?

Photo by julian mora on Unsplash

How Inheritance is achieved using Prototype in Javascript?

prototype and constructor

  • Every function has a prototype field which is an object and has a field called constructor which points to the main function. And, since main function again is an object and has prototype and again has constructor so it creates a circular reference.
  • Let's take a simple example of a function
function myfunc() {
  return "hello world";
}

console.log(myFunc); // prints function () { return "hello"; }
console.log(myFunc.prototype); // prints an object
console.log(myFunc.prototype.constructor); // prints function () { return "hello"; }
console.log(myFunc.prototype.constructor.prototype.constructor.prototype.constructor.prototype.constructor); // prints function () { return "hello"; }
  • As we can see from the last console.log(), Its endless

Let's do some Inheritance

  • For our example, Hero is a base class and has properties name and greet. Warrior is going to inherit from Hero.
  • We are going to use .call() to make the inheritance work in this case.
function Hero(name) {
  this.name = name;
  this.greet = function () {
    return this.name + " says hello";
  };
}

function Warrior(name) {
  Hero.call(this, name);
  this.weapon = "sword";
}

console.log(new Hero("h").greet()); // h says hello
console.log(new Warrior("w").greet()); // w says hello
  • As we can see, Its working perfectly. The name and greet are available inside Warrior and Healer objects.
  • But there is a problem here, every instance that we create of Hero will have their separate greet method. So, Let's say if we created 1000 Hero instances, There are 1000 greet methods present in memory. And same goes for Warrior as well. That's not good. So, How can we solve this?

Entering prototype

  • Since every function has a prototype field and is an object. We can attach some property to it, which will be accessible to all the instances of this function.
  • Attaching property to prototype, will have that property only once in memory, no matter how many instances we create. Why? Please refer prototype and proto.
function Hero(name) {
  this.name = name;
}

Hero.prototype.greet = function () {
  return this.name + " says hello";
};

function Warrior(name) {
  Hero.call(this, name);
  this.weapon = "sword";
}


console.log(new Hero("h").greet()); // h says hello
console.log(new Warrior("w").greet()); // throws Error .greet is not a function
  • So, greet function on Hero is working correctly but, its not working on Warrior. So, what can we do to solve it? We can take prototype field of Warrior and point it to copied object of Hero.prototype. Like Warrior.prototype = Object.create(Hero.prototype);
  • What Object.create() does is It takes an object as an argument and returns an empty object with proto pointing to the passed object.

function Hero(name) {
  this.name = name;
}

Hero.prototype.greet = function () {
  return this.name + " says hello";
};

function Warrior(name) {
  Hero.call(this, name);
  this.weapon = "sword";
}

Warrior.prototype = Object.create(Hero.prototype);

console.log(new Hero("h").greet()); // h says hello
console.log(new Warrior("w").greet()); // w says hello
  • It worked because now Warrior.prototype is an object that has greet property available in its proto. So, Even if we create a lot of instances of Warrior, only 1 greet function is there in memory and that is attached to Hero.prototype.
console.log(Warrior.prototype.__proto__ === Hero.prototype); // prints true
  • We could have also used Warrior.prototype = new Hero() instead of Object.create(). This would also have worked.
  • Now, we have achieved inheritance. But, Still there is a problem.

Constructor problem

  • Let's say if we want to provide a clone functionality to Warrior and all the other entities that will inherit from Hero. How can we do that?
  • Now, we are going to use this.constructor to achieve this. As, we have already seen, every prototype object has constructor field which is nothing but the function itself.
function Hero(name) {
  this.name = name;
}

Hero.prototype.greet = function () {
  return this.name + " says hello";
};

Hero.prototype.clone = function () {
  return new this.constructor(this.name);
};

function Warrior(name) {
  Hero.call(this, name);
  this.weapon = "sword";
}

Warrior.prototype = Object.create(Hero.prototype);

console.log(new Hero("h").greet()); // h says hello
console.log(new Warrior("w").greet()); // w says hello
console.log(new Warrior("w").clone() instanceof Warrior); // false
console.log(new Warrior("w").clone() instanceof Hero); // true
  • So, whatever cloned object we create of Warrior is actually an instance of Hero. Why? Because of Warrior.prototype = Object.create(Hero.prototype); Warrior.prototype.constructor has now become Hero function (i.e Hero.prototype.constructor). So, we just have to set it back. i.e Warrior.prototype.constructor = Warrior;
  • So, now working code is
function Hero(name) {
  this.name = name;
}

Hero.prototype.greet = function () {
  return this.name + " says hello";
};

Hero.prototype.clone = function () {
  return new this.constructor(this.name);
};

function Warrior(name) {
  Hero.call(this, name);
  this.weapon = "sword";
}

Warrior.prototype = Object.create(Hero.prototype);
Warrior.prototype.constructor = Warrior;

console.log(new Hero("h").greet()); // h says hello
console.log(new Warrior("w").greet()); // w says hello
console.log(new Warrior("w").clone() instanceof Warrior); // true
  • So , now when we called the clone() on Warrior instance. It tried to run the new this.constructor(this.name);, It checked if the constructor field is present in the instance and then it checked inside proto which is now pointing to the prototype field of Warrior function, which has the constructor as explained.