Understanding Prototypes and Prototypal Inheritance in JavaScript

A look at prototypes and how we can use them for inheritance in JavaScript.

Gemma Croad
4 min readJul 30, 2020
JavaScript code snippet of a function and then some prototype modifications.

This article is going to look at object prototypes and how we can use constructor functions to extend prototypes in new objects.

JavaScript is a prototype-based language, which means object properties and methods can be shared through objects that have the ability to be cloned and extended. This is known as prototypal inheritance.

In my previous article, Understanding objects in JavaScript, I went over how to create an object, how to access object properties and how to modify them. Now we are going to learn how prototypes can be used to extend objects.

Nearly all objects in JavaScript are instances of Object which sit on the top of a prototype chain. This means they have an internal property called prototype. The prototype is a reference to another object and it gets used whenever JavaScript can’t find the property it is looking for on the current object.

We can check this if we create a new, empty object.

const newObject = {};console.log(newObject);

If we look in the console we can see the __proto__ property which exposes the internal prototype of the object. This consists of several built-in properties and methods.

{}
__proto__:
constructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()

To get the prototype of this new object we use the getPrototypeOf() method.

const newObject = {};
const prototype = Object.create(newObject);
console.log(Object.getPrototypeOf(prototype) === newObject); // true

Let’s look at a more detailed code example. We are going to create a Sandwich constructor. We will then create an instance of Sandwich and assign it to a new variable called mySandwich.

function Sandwich(fillings = [], customer, bread) {
this.fillings = fillings;
this.customer = customer;
this.bread = bread;
this.describe = function() {
return `This sandwich is for ${this.customer} with the toppings ${this.fillings.join(', ')} on ${this.bread} bread`;
}
}
const mySandwich = new Sandwich(['cheese', 'ham', 'lettuce', 'tomato', 'cucumber'], 'Gemma', 'wholemeal');console.log(mySandwich.describe());// This sandwich is for Gemma with the toppings cheese, ham, lettuce, tomato, cucumber on wholemeal bread

If we look at mySandwich in the console we can see the describe function exists as an instance property.

mySandwich
Sandwich {fillings: Array(5), customer: "Gemma", bread: "wholemeal", describe: ƒ}
bread: "wholemeal"
customer: "Gemma"
describe: ƒ ()
fillings: (5) ["cheese", "ham", "lettuce", "tomato", "cucumber"]
__proto__: Object

Let’s create another instance of Sandwich using some different parameters.

const mySandwichVeggie = new Sandwich(['lettuce', 'tomato', 'onion', 'pickles', 'olives', 'capscicum', 'spinach', 'carrot'], 'Sam', 'white');console.log(mySandwichVeggie.describe());// This sandwich is for Sam with the toppings lettuce, tomato, onion, pickles, olives, capscicum, spinach, carrot on white bread

What happens if we directly compare our newly created sandwich describe() function with the original mySandwich describe method.

mySandwich.describe === mySandwichVeggie.describe // false

They are not the same function; we are duplicating functionality. If we create 50 sandwiches we would have 50 copies of the same function which could potentially cause performance issues and make our code hard to maintain.

To solve the duplication issue we can add this functionality to the prototype.

Sandwich.prototype.describe = function() {
return `This sandwich is for ${this.customer} with the toppings ${this.fillings.join(', ')} on ${this.bread} bread`;
}

Now if we look at mySandwich or mySandwichVeggie, describe() no longer exists as an instance property, it is, however, visible in the __proto__.

mySandwich
Sandwich {fillings: Array(5), customer: "Gemma", bread: "wholemeal"}
bread: "wholemeal"
customer: "Gemma"
fillings: (5) ["cheese", "ham", "lettuce", "tomato", "cucumber"]
__proto__:
describe: ƒ ()
constructor: ƒ Sandwich(fillings = [], customer, bread)
__proto__: Object

This means that every time we create a new Sandwich we now have access to the describe() function.

JavaScript will check for a property on the instance first and if it doesn’t exist it will check the prototype.

If we add a new instance property to the prototype and to the instance itself we can see this in action.

function Sandwich(fillings = [], customer, bread) {
this.fillings = fillings;
this.customer = customer;
this.bread = bread;
this.price = '5.50';
}
Sandwich.prototype.price = '4.00';mySandwich
Sandwich {fillings: Array(5), customer: "Gemma", bread: "wholemeal", price: "5.50"}
bread: "wholemeal"
customer: "Gemma"
fillings: (5) ["cheese", "ham", "lettuce", "tomato", "cucumber"]
price: "5.50"
__proto__: Object

The price of mySandwich is 5.50 because JavaScript doesn’t need to go to the prototype, the property exists on the instance.

Summary

  • JavaScript is a prototype-based language
  • Prototypal inheritance means object properties and methods can be shared through objects that have the ability to be cloned and extended
  • Nearly all objects in JavaScript are instances of Object which sit on the top of a prototype chain which means they have an internal property called prototype
  • The prototype is a reference to another object and it gets used whenever JavaScript can’t find the property it is looking for on the current object
  • JavaScript will first check for a property on the instance then check the prototype

--

--

Gemma Croad

Software Engineer, a11y and user advocate, experienced remote worker, creative coder, lover of all things front-end.