JavaScript: prototype based inheritance explained.

A deep look at JavaScript prototype based inheritance with explanations and code examples.

JavaScript Logo
JavaScript Logo

Introduction

For performance reasons, when both computers and browsers were much less powerful, the engineers behind JavaScript decided to adopt a prototype based inheritance instead of the classical inheritance approach.

But, why this? What's the advantage of this approach? emoji 🤔

The gain is that prototype inheritance is able to deliver better performance mainly because of its much smaller memory footprint in respect to the classic approach. In fact, instead of copying class methods and properties in every newly instantiated object (memory expensive), the prototype approach share them, making the application able to use a much lower amount of memory and making it suitable for the use in limited contexts (I'm looking at you browser, at least in your young age emoji 😉).

Mmhm, what's the catch? emoji 🤔

While this reduces the memory required by the application to run, it has some working differences compared to the classical way that you need to be aware of.

Objects links to each others to share methods and properties, and this introduces major architectural differences:

  • it doesn't exist a native set of keywords to easily declare and work with classes (see [1]);
  • parent objects can be modified at runtime, and these changes apply immediately to all objects that inherit from the same parents! [2]
  • objects themselves have a prototype (Object.prototype), which provides useful functions to work with objects, its prototype is null and usually it closes the prototype chain. [3]

The prototype chain emoji 🔗

These links create a chain that starts with the child, then the parents, and eventually a null that closes it.

When you try for example to invoke a function on your child object, if that function has been defined in the child, then it will be immediately invoked, otherwise the whole prototype chain will be scanned, link after link, object after object, until the function requested is found or null (the end of the chain) is reached, returning in the latter case undefined.

//The prototype chain.
var parent2 = function(){};
parent2.prototype.log = function(){
	console.log('parent2');
}

var parent1 = function(){};
parent1.prototype = new parent2();

var child = function(){};
child.prototype = new parent1();

var childObj = new child();
// === {} --proto--> {} --proto--> {} --proto--> {log: fn} --proto--> Object.prototype --proto--> null

childObj.log(); //'parent2' -> Found in `parent2`.

parent1.prototype.log = function(){
	console.log('parent1');
}
// === {} --proto--> {} --proto--> {log: fn} --proto--> {log: fn} --proto--> Object.prototype --proto--> null

childObj.log(); //'parent1' -> Found in `parent1`.

child.prototype.log = function(){
	console.log('child');
}
// === {} --proto--> {log: fn} --proto--> {log: fn} --proto--> {log: fn} --proto--> Object.prototype --proto--> null

childObj.log(); //'child' -> Found in `child`.

Cool! How can I be productive with it? emoji 🎉

There are different ways to declare and work with prototype inheritance in JavaScript, and we are going to see them all in detail. emoji 🐎

This is the parent-child structure that we will create in our examples:

JavaScript prototype chain
JavaScript prototype chain

Nice, easy and explicative, isn't it? Now to the code. emoji 😏

emoji 👉 Using functions and the new keyword.

The new keyword does four important things:

  • Creates a new object;
  • Links its prototype to function.prototype, where function is the function passed to the new keyword;
  • Executes the function code after setting its this to the newly crate object;
  • Returns that object. [4]
var parent = function(){};
parent.prototype.getValue = function(){
	return 5;
}

var child = function(){};
child.prototype = new parent();
// === {} --proto--> {getValue: fn} --proto--> Object.prototype --proto--> null

child.prototype.calculate = function(){
	return this.getValue();
}
// === {calculate: fn} --proto--> {getValue: fn} --proto--> Object.prototype --proto--> null

var obj = new child();
// === {} --proto--> {calculate: fn} --proto--> {getValue: fn} --proto--> Object.prototype --proto--> null

obj.calculate() === 5; //True.

emoji 👉 Using the function Object.create.

var parent = {};
parent.getValue = function(){
	return 5;
}

var child = Object.create(parent, {});
// === {} --proto--> {getValue: fn} --proto--> Object.prototype --proto--> null

child.calculate = function(){
	return this.getValue();
}
// === {calculate: fn} --proto--> {getValue: fn} --proto--> Object.prototype --proto--> null

var obj = Object.create(child, {});
// === {} --proto--> {calculate: fn} --proto--> {getValue: fn} --proto--> Object.prototype --proto--> null

obj.calculate() === 5; //True.

emoji 👉 Using the function Object.setPrototypeOf.

var parent = {};
parent.getValue = function(){
	return 5;
}

var child = Object.setPrototypeOf({}, parent);
// === {} --proto--> {getValue: fn} --proto--> Object.prototype --proto--> null

child.calculate = function(){
	return this.getValue();
}
// === {calculate: fn} --proto--> {getValue: fn} --proto--> Object.prototype --proto--> null

var obj = Object.setPrototypeOf({}, child);
// === {} --proto--> {calculate: fn} --proto--> {getValue: fn} --proto--> Object.prototype --proto--> null

obj.calculate() === 5; //True.

emoji 👉 Using the class syntax sugar. [1]

class Parent{
	getValue(){
		return 5;
	}
};

class Child extends Parent{
	calculate(){
		return this.getValue();
	}
};

var obj = new Child();

obj.calculate() === 5; //True.

emoji 📚 Footnotes

[1] The class keyword is introduced in ES2015, but it is just a syntax sugar, in fact behind the scenes JavaScript still remains prototype based.

[2] Code sample:

//Modify parents at runtime.
var parent = function(){};
parent.prototype.log = function(){
	console.log('parent');
}

var child1 = new parent(); //child1.__proto__ === parent.prototype
var child2 = new parent(); //child2.__proto__ === parent.prototype

child1.log(); //'parent'
child2.log(); //'parent'

parent.prototype.log = function(){
	console.log('modified!');
}

child1.log(); //'modified!'
child2.log(); //'modified!'

[3] Sometimes you may want to create a "clean" object, an object which its prototype doesn't link to Object.prototype, but to null. You can achieve using Object.create or Object.setPrototypeOf.

//Create a new object which its prototype links to null.
var objToProtoNull1 = {};
var objToProtoNull2 = Object.create(null);
var objToProtoNull3 = Object.setPrototypeOf({}, null);

objToProtoNull1.toString === undefined; //False! It inherits the function from Object.prototype.
objToProtoNull2.toString === undefined; //True.
objToProtoNull3.toString === undefined; //True.

[4] More on the new keyword.

emoji 📚 Further readings

Inheritance and the prototype chain

Object.create()

For updates, insights or suggestions, feel free to post a comment below! emoji 🙂


Responses

Latest from Web Engineering browse all

JavaScript: in depth practical explanation on closures. | cover picture
JavaScript: in depth practical explanation on closures.
Created on 21 July 2018.
A deep look at JavaScript closures with explanations and hands-on code examples.
JavaScript: differences between using var, let and const. | cover picture
JavaScript: differences between using var, let and const.
Created on 19 June 2018.
A close look and explanation at how to properly use the new ES6 JavaScript variable declaration keywords.
JavaScript: how to find a key-value pair in a nested object. | cover picture
JavaScript: how to find a key-value pair in a nested object.
Created on 17 June 2018.
How to recursively traverse a JavaScript nested and unordered object to find if a value exists at a specific key.
×