Prototypal Inheritance in JavaScript

Abhijith
5 min readJun 2, 2024

--

Inheritance in classic OOP means passing down properties and methods from a parent to a child so that it can reuse methods and properties defined on parent. JavaScript implements inheritance through objects. Every object in JavaScript has an internal link ([[Prototype]]) to an object called prototype which in turn might have a prototype of its own and so on until the prototype points to null. null has no prototype and acts as the end of the prototype chain.

So how do you access this prototype?
1. anyObject.getPrototypeOf(anyObject)
2. anyObject.__proto__

The former is the standard according to the ECMAScript standard, while the latter is non-standard but widely implemented by most browsers. However, both point to the same thing: the prototype ( [[Prototype]]).

inheriting Properties

When we try to access a property from a JavaScript object, it is first searched on the object itself. If it is not found there, it is searched on the object’s prototype. This process continues up the prototype chain until it reaches null. If the property is still not found, it will return undefined.

const country = {
name : "India",
id: "1",
__proto__: {
planet: "Earth"
}
}

console.log(country.name);
// returns "India" because the
// property is found on the object itself

console.log(country.id);
// returns "1" because the
// property is found on the object itself

console.log(country.planet);
// returns "Earth" because the property was not found on the object itself,
// so it is checked on its [[Prototype]], which is the object containing the desired property

Inheriting Methods

JavaScript doesn’t have methods in the same way as other object-oriented programming languages. Instead, everything is a property. Consequently, both methods and properties are inherited as properties.

When an inherited function is executed, the value of this points to the inheriting object

const countryPrototype = {
singNationalAnthem() {
console.log("singing " + this.nationalAnthem)
}
}

country.singNationalAnthem() //singing ""
// This is because the this binding here is the object nation and has own
// property singNationalAnthem and nationalAnthem

const country = {
name: "India",
id: "1",
nationalAnthem: "Jana Gana Mana....",
__proto__: countryPrototype
}

country.singNationalAnthem() //singing Jana Gana Mana....
// Here the method singNationalAnthem is not found on the object country
// so it is looked up on its [[Prototype]] nation.
// it prints Jana Gana Mana.... becuase the this binding refers to the
// object country and its has an own property nationalAnthem

Constructor functions

The power of prototypes is that we can reuse a set of properties if they should be present on every instance. For example if we need to create an array of countries and the method “singNationalAnthem” must be present on all the objects we would be tempted to create objects like below

const germany = {
name: "Germany",
id: "2",
nationalAnthem: "Einigkeit und Recht und Freiheit....",
__proto__: nation
}

const netherlands = {
name: "Netherlands",
id: "3",
nationalAnthem: "Wilhelmus van Nassouwe"
__proto__: nation
}

so every time we want to create a country we were manually creating an object and setting the [[Prototype]] (__proto__) property which is not an efficient way. This is where constructor functions comes to help, in sharing properties among all the instances of type Country.

function Country(name, id, nationalAnthem) {
this.name = name;
this.id = id;
this.nationalAnthem = nationalAnthem;
}

Country.prototype.singNationalAnthem = function () {
console.log("singing " + this.nationalAnthem)
}
// Now all the instances of Country created using the constructor function
// will inherit the method singNationalAnthem

const india = new Country("India", "1", "Jana Gana Mana....")

india.singNationalAnthem() // singing Jana Gana Mana....

const germany = new Country("Germany", "2", "Einigkeit und Recht und Freiheit....")

germany.singNationalAnthem() //singing Einigkeit und Recht und Freiheit....

one important thing here is that the Country.prototype is not the [[Prototype]] of the function Country instead it’s prototype is actually Function.prototype, But all the instances/Objects created from the constructor function Country (india and germany as in the example) will have their [[Prototype]] set to Country.prototype;

now the prototype chain will looks something like this

india -> Country.prototype -> Object prototype -> null

Country.prototype -> Function prototype -> Object prototype -> null

Prototype chain of india object
prototype chain of constructor function Country

We can achieve the same result using classes. In JavaScript, classes are actually syntactic sugar over constructor functions.

class Country {
constructor(name, id, nationalAnthem) {
this.name = name;
this.id = id;
this.nationalAnthem = nationalAnthem;
}

singNationalAnthem() {
console.log("singing " + this.nationalAnthem)
}
}

const india = new Country("India", "1", "Jana Gana Mana....");

const germany = new Country("Germany", "2", "Einigkeit und Recht und Freiheit....")

india.singNationalAnthem() // singing Jana Gana Mana....

germany.singNationalAnthem() // singing Einigkeit und Recht und Freiheit....

Using Object.setPrototypeOf to set Prototype

The above examples set prototypes for instances created from a constructor function, but in classical OOP inheritance, you have a base class that your child classes extend. This behavior can also be achieved with Constructor functions

function Nation() {
this.name = ""
}

Nation.prototype.singNationalAnthem = function() {
console.log("singing " + this.nationalAnthem);
}

function Country(name, id, nationalAnthem) {
this.name = name;
this.id = id;
this.nationalAnthem = nationalAnthem;
}

Object.setPrototypeOf(Country.prototype, Nation.prototype);

const indiaObj = new Country("India", "1", "Jana Gana Mana...");

indiaObj.singNationalAnthem(); //singing Jana Gana Mana...

now the prototype chain looks something like this

indiaObj -> Country.prototype -> Nation.prototype -> Object prototype -> null

This same thing can be written using classes as below

class Nation {
constructor(name, id, nationalAnthem) {
this.name = name;
this.id = id;
this.nationalAnthem = nationalAnthem;
}

singNationalAnthem() {
console.log("singing " + this.nationalAnthem);
}
}

class Country extends Nation {
constructor(name, id, nationalAnthem, states) {
super(name, id, nationalAnthem);
this.states = states;
}

}

const india = new Country("India", "1", "Jana Gana Mana...", 28);

india.singNationalAnthem()

Default Prototype Links

In JavaScript every object created will have default [[Prototype]] set to them implicitly

for Objects it is Object Prototype

for functions it is Function prototype

for Arrays it is Arrays Prototype ….

The Array methods such as map, filter, forEach …are actually properties/ methods defined on the Array prototype and that's why they are available on every Arrays instances.

Conclusion

JavaScript's inheritance model is different from other languages like Java/ C++ and might be little confusing at start. Everything is either an object (instance) or a function (constructor), and even functions themselves are instances of the Function constructor. Classes are just syntactical sugar over this model.

The Function constructor has a special property called prototype which when used with new keyword helps to set the prototype of the instantiated objects and because of the dynamic nature of the language even this prototype can be changed at run time.

--

--