Jay Gould

Javascript fundamentals - Object prototypes and inheritance

September 30, 2019

Intro

Since the intro of Javascript ES6 classes I’ve not had much need to venture back in to structuring web apps with functions and prototypes, but I’ve recently had to work on some legacy code from an old project and was surprised by how much I had to brush back up on. There’s a lot to take in with JS prototypes, and even though the class construct is useful and stops direct usage of JS prototypes, the “class” is just syntactic sugar and prototypes are still used in the background.

So here’s a post covering the basics of Javascript prototypes along with inheritance, and a little about composition to finish it off.

Intro to prototypes

In JS, everything is an object. You may have heard this saying before and it’s largely true because of the nature of the prototype tree in Javascript. Javascript prototypes are “the mechanism by which JavaScript objects inherit features from one another” (MDN) - in other words, it’s Javascript’s approach to inheritance. All objects have an associated “prototype object” which is the objects template containing it’s inherited methods and properties.

For example, the following is how we can declare an object:

let object = {
  property: "value",
}
// which is also the same as...
let object = new Object({
  property: "value",
})

console.log(object.property) // outputs "value"

Arrays and functions behave like objects too, as we can do things like the following:

let arrayLength = [1,2,3].length
// or...
let myFunction = function holyCow () {
  ...
}

console.log(myFunction.name) // outputs "holyCow"

Arrays and functions are able to use the object dot notation to access their prototype properties (as well as internal, custom properties if you’re a custom object). These properties like .length or .valueOf() for example, exist because they are defined on the object’s prototype (actually they are defined on an object’s constructor function such as Array() or Object(), but are passed down the prototype tree, which I’ll cover more later).

The available prototypes for objects, arrays, numbers etc. can be viewed in multiple ways. You can look in the browser console for example:

array proto

You can also access in JS by using Object.getPrototypeOf():

let obj = []
console.log(Object.getPrototypeOf(obj)) //outputs all of the array's properties like .length, .filter, .map etc.

There are also ways to get and set prototypes in Javascript. This leads on to JS prototypal inheritance, which is a core part of object oriented Javascript (since there’s no “proper” class based way of OO). But before I get on to that, I want to give a brief overview of a confusing part of Javascript - the different ways to access an object’s prototypes.

getPrototypeOf and proto

I like to log whole objects/array to see their prototypes as it seems more flexible and intuitive to me, but if you just want to see the prototype of an item, you can use getPrototypeOf and __proto__. They both do similar things, but getPrototypeOf is meant to be used over __proto__ as it’s the current standard..

let myArr = ['item', 'item2'];
console.log(Object.getPrototypeOf(myArr))
outputs all of the array's properties like .length, .filter, .map etc.

The above example shows how we can get the prototype of an array. We know this is an array as we are instantiating it as one so it might not be obvious why this is useful. It’s more useful if we want to get the prototype of something we have created via a constructor function perhaps, as we’ll be able to then see all the custom methods we’ve added.

Another thing to note is that the following are both exactly the same:

let myArr = ["item", "item2"]
console.log(Object.getPrototypeOf(myArr)) // this...
console.log(Array.prototype) // .. is exactly the same as this

They are the same because we are getting the prototype of the instance from which myArr inherits from, which is the Array prototype in this case.

Prototypes and the constructor function

Since the early days of Javascript you were able to get and set prototypes using the prototype property of a constructor function. Constructor functions are used to create new objects, and are used under the hood to pass down the available prototypes to instances of the constructors. For example:

Object.prototype.myNewFunction = function () {
  return "Wooh!"
}
let newObject = {}
console.log(newObject.myNewFunction()) // outputs 'Wooh!'

The same can be done with Array.prototype as Array is the constructor function. This can be proven by logging console.log(Array). which shows it’s value as a function.

W2Schools has a useful page on constructors which list the built in Javascript constructor types.

The above example extends the native Object constructor function (the function which is responsible for creating new object wrappers with the use of “new Object()”) by adding a new method to the Object prototype. This new addition to object (myNewFunction) can be seen in the console by logging the following:

console.log(Object.prototype)

It’s important to note that with Object.prototype, the ".prototype" property can only be accessed because it’s the constructor, Object. In contrast, let’s say we have the following:

let myObject = {}
console.log(myObject.prototype) // outputs "undefined"

The .prototype property does not exist on myObject because it’s not a constructor. Instead, myObject is an instance of the Object constructor. BUT, we can still access the prototypes which are inherited from the Object prototype, by using Object.getPrototypeOf(myObject) as I mentioned earlier (more on that later).

It’s also worth mentioning at this point that the prototypes on a constructor are not exclusive to Object, Array and other native JS constructors. They can also be used with custom constructors:

function MyConstructor() {
  this.jaysProperty = "Jays property value"
}
console.log(MyConstructor.prototype)

The above will output the properties available to all functions - the constructor. It won’t show the jaysProperty though as that’s not on the constructor function’s prototype yet - it’s simply a member of an instance of MyConstructor. In other words, if we ran let foo = new MyConstructor();, foo will have access to jaysProperty.

We can add prototypes to our custom constructor though:

function MyConstructor() {
  this.jaysProperty = "Jays property value"
}
MyConstructor.prototype.anotherProperty = () => {
  return "another property"
}
console.log(MyConstructor.prototype)

This will output the new anotherProperty prototype addition to MyConstructor, and when MyConstructor is finally initialised, it will show both the property available to the instance (jaysProperty), as well as the property available to the prototype (anotherProperty):

function MyConstructor() {
  this.jaysProperty = "Jays property value"
}
MyConstructor.prototype.anotherProperty = () => {
  return "another property"
}
let myInstance = new MyConstructor()
console.log(myInstance) // outputs the property of the instance and the prototype

This raises the good old question - is it better to have properties defined on the constructor, or on the prototype? I’ll cover that a bit later.

Constructor inheritance (and prototype inheritance)

One final point on constructor functions is that they can inherit from other constructors.

function MyParentConstructor() {
  this.parentProperty = "Parent property"
  this.parentMethod = function () {
    return "A function call"
  }
}

function MyChildConstructor() {
  MyParentConstructor.call(this)
}

let myChildInstance = new MyChildConstructor()
console.log(myChildInstance.parentProperty) // outputs "Parent property"
console.log(myChildInstance.parentMethod()) // outputs "A function call"

The example above shows how we can have a constructor function inherit the properties of another constructor. I like to see this as like a parent to child relationship - kind of like class inheritance. This uses the call() method to basically run the parent constructor function in the context (with this) of the child constructor.

The above inheritance shows how we can inherit the properties which are defined in the constructor, but let’s say we have the following:

function MyParentConstructor() {
  this.parentProperty = "Parent property"
  this.parentMethod = function () {
    return "A function call"
  }
}

MyParentConstructor.prototype.anotherProperty = () => {
  return "another property"
}

function MyChildConstructor() {
  MyParentConstructor.call(this)
}

let myChildInstance = new MyChildConstructor()
console.log(myChildInstance.anotherProperty()) // this will throw an error!

This log above will throw an error, because it’s attempting to access a property of the “parent” constructor’s prototype which is not part of the child’s prototype chain (because they are two separate constructors which are only currently linked by a simple function call). The real power of prototypes and constructors come from sharing the prototypes as well as the properties within:

function MyParentConstructor() {
  this.parentProperty = "Parent property"
  this.parentMethod = function () {
    return "A function call"
  }
}

MyParentConstructor.prototype.anotherProperty = () => {
  return "another property"
}

function MyChildConstructor() {
  MyParentConstructor.call(this)
}

let myChildInstance = new MyChildConstructor()

MyChildConstructor.prototype = Object.create(MyParentConstructor.prototype)

// the following can also be used to produce the same outcome, but it's recommended against
// for performance reasons according to [MDN docs on setPrototypeOf](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf).
// Object.setPrototypeOf(MyChildConstructor.prototype, MyParentConstructor.prototype)

console.log(myChildInstance.anotherProperty()) // output: "another property"

That’s it! We can now access the prototype of one constructor from another. We have manually set the prototype of the constructor above, and the beauty of this is that we can still add more properties to both MyParentConstructor and MyChildConstructor - that is to say that we’re not limited by adding prototype properties just because we’ve assigned the prototype, but obviously we must ensure that…

MyChildConstructor.prototype = Object.create(MyParentConstructor.prototype)

… is called before setting any more prototypes on MyChildConstructor.

Basic object inheritance

The above sections go in to setting and inheriting constructor functions, but functionality and properties can be shared between object literals as well:

let parentObject = {
  parentFunction() {
    return "i am seen everywhere..."
  },
}
let basicObject = {
  sayHi() {
    return "hi!"
  },
}
Object.setPrototypeOf(basicObject, parentObject)
console.log(basicObject.sayHi()) // outputs "hi!"
console.log(basicObject.parentFunction()) // outputs "i am seen everywhere..."

As these are not constructor functions (they are instances of the Object object), we can’t access a .prototype property directly to inherit, so we use the setPrototypeOf() function as mentioned earlier. I mentioned earlier that there’s also the __proto__ value which is kind of the old way. Well this is another way to achieve the same thing using __proto__ and Object.create():

basicObject.__proto__ = Object.create(parentObject.__proto__)

which can be written more conveniently as…

let parentObject = {
  parentFunction() {
    return "i am seen everywhere..."
  },
}
let basicObject = {
  sayHi() {
    return "hi!"
  },
  __proto__: Object.create(parentObject.__proto__),
}

Although this works, it’s better practice to use the first solution of setPrototypeOf().

When would you want to use prototypes?

I personally don’t use prototypes that much - I only go for them if they are performing something very specific (and often quite small) that I want to be on all instances of an object. The operations I add are often static, and don’t affect anything else in the code base other than just that instance.

Also as I mentioned before, it makes sense to add a prototype to something which is instantiated a lot, as the functionality is not attached manually to each instance when the instance is created. Instead, on a prototype the functionality is added just once when the program starts.

Some people do things differently, but I like to extend things like the Array and Function constructors sometimes. Not so much now because we already have so much helpful functionality already with map, filter, etc… but if I’m doing some sort of deep object comparison perhaps.

Composition over inheritance

This post covers inheritance in a little detail for both object literal inheritance, and inheritance using prototypes of constructor functions. I’ve covered inheritance using a non-class based approach, for which inheritance is a perfectly valid paradigm.

However, a lot of developers don’t like inheritance at all with any programming language. The argument is that with inheritance we are making a lot of assumptions about the structure of our classes (or prototypes), specifically the methods and properties they inherit from one another.

Instead, people argue that it’s best to view things in terms of what they do rather than what they are, allowing us to easily add and remove a “thing“‘s capabilities (functionality).

Rather than going in to coding examples, there’s an excellent YouTube video which covers in great detail.

Thanks for reading!


Senior Engineer at Haven

© Jay Gould 2023, Built with love and tequila.