Prototypal Inheritance in Javascript
Protoypal Inheritance in Javascript
Prototypal inheritance is a very important concept in Javascript for Object Oriented Programming (OOP). In Javascript because Arrays and Functions are just Objects we already have an example of Prototypal inheritance in some of the basic objects we use. This means that both Arrays and Functions have access to the properties and methods of the Object class through the inheritance chain. We can see this in our code like this:
const array = []
array.__proto__
// This will return what looks like an array with methods like push, pop, reduce, and so on. This is just a base array.
// The __proto__ accessor allows us to look one step up in our inheritance chain.
// While this isn't recommended to use in actual code - actually, you should never use it - it is good to demonstrate how the language works
// We can also go further and use proto again, which will return us an object
array.__proto__.__proto__
// this returns the base object that everything in Javascript is created from including Functions and Arrays.
We can also do this with any Function and Object.
function a()
a.__proto__.__proto__
// this will return an object just as you see in our previous example with the array
const obj = {}
obj.__proto__
// this also returns an object as with our two previous examples.
Now you might be wondering why Javascript has this feature and what practical use it has. This is a feature that isn't commonly found in other languages, such as Java and C. Javascript actually doesn't have classes at all, but only prototypal inheritance. Using this inheritance in our objects leads us to similar behaviour to how closures work. For example, if you try to call a function or access data on an object and it's not found, javascript will then look higher up in our inheritance chain to find what we're after.
let dragon = {
name: 'Bob',
fire: true,
fight() {
return 5
},
sing() {
if (this.fire) {
return `I am ${this.name}, the breather of fire`
}
}
}
let lizard = {
name: 'Billy',
fight() {
return 1
}
}
// One way (not recommended but useful for this demonstration) we can make lizard inherit from the dragon object is to do this
lizard.__proto__ = dragon; // sets our prototype to dragon including all of dragons properties
lizard.sing(); // will be able to sing as it inherits the properties and methods of dragon object.
We can see if an object is the prototype of another object with this method:
dragon.isPrototypeOf(lizard); // returns true as dragon is the prototype of lizard
lizard.isPrototypeOf(dragon); // returns false as lizard derives from dragon, not the other way around
Javascript will also look up multiple chains to find what it is after, not just one level up. If the property or method cannot be found, we then get 'undefined' or some sort of error. We can also see what properties are shared from the inherited object in these ways:
// will log all of the properties lizard has that are also found in dragon
for (let prop in lizard) {
// hasOwnProperty is in the base object, which is two levels up in this case
if(lizard.hasOwnProperty(prop)) {
console.log(prop);
}
}
One benefit of this prototypal inheritance is that we can have many objects that inherit another which all point to the same place in memory for their inherited properties. This can save a lot of memory if we have a lot of objects, as the properties and methods will just use one instance instead of copying these for each inherited object.
We earlier mentioned that we should never use proto for any reason as this leads to performance issues. So how do we derive inheritance in our objects without it? We can do this by creating new objects of the desired type with Object.create().
let human = {
mortal: true
}
let socrates = Object.create(human) // creates a prototype chain up to human
console.log(human.isPrototypeOf(socrates))
When it comes to using this concept in a real case scenario we can also do some really cool things, like adding new methods or properties to existing protoypes. For functions, this can be done using .prototype
// Say we want to add new functions to our Date object, such as finding the next year or the last year.
// add a new function to the prototype for Date object.
Date.prototype.getLastYear = function() {
return this.getFullYear() -1;
}
// note that we have to use the function() keyword here, and cannot use modern methods like arrow functions as we want the 'this' keyword to be lexically scoped. The 'this' keyword must refer to the Date object.
Date.prototype.getNextYear = function() {
return this.getFullYear() +1;
}
new Date().getLastYear();
new Date().getNextYear();
We can also do this to add or change functionality to other objects like Arrays.
// override the map function
Array.prototype.map = function() {
let arr = [];
// loop uses 'this' keyword to refer to our array [1,2,3]
for (let i = 0; i < this.length; i++) {
arr.push((this[i] + '🗺'))
}
return arr;
}
// will concat a map emoji to the end of each array item
console.log([1,2,3].map())
Generally, we should not actually modify any existing methods we have as this can cause very unexpected behaviour for programmers, but if we need to we can absolutely add new functionality. All of these examples using prototype is actually an old way of using Javascript however just to see the fundamentals of the language under the hood. There are better modern ways of doing this like the class keyword which we will cover in another post.