Aman Explains

Settings

An Easy Explanation to Prototypal Delegation in JavaScript

February 15, 2021• ☕️ 4 min read

JavaScript language stands on two pillars: functional programming and prototypal delegation. The addition of classes in JavaScript is mere a syntactic sugar to give it Object-oriented programming feel: unwrap them and you will find functions inside.

Prototypal Delegation

Prototypal delegation is all about delegating the responsibility to the prototype higher up in the [[Prototype]] chain.

function foo(){}

Object.getPrototypeOf(foo) === Function.prototype; // true
Object.getPrototypeOf(Function.prototype) === Object.prototype; // true
Object.getPrototypeOf(Object.prototype); // null

The prototype chain will look like this:

foo —> Function.prototype —> Object.prototype —> null

In simple words, if you try to look for a property that is not owned by the object, the JavaScript engine will traverse up its prototype chain until it finds it. Let’s see an example to understand it.

const recipe = { name: "Garlic Naan" };

const recipeBook = {
  getRecipeName() {
    console.log(this.name);
  },
};

// Set 'recipeBook' as the prototype of 'recipe'
Object.setPrototypeOf(recipe, recipeBook);

// Prototypal delegation in action
recipe.getRecipeName(); // prints 'Garlic Naan'

The prototype chain will look like this:

recipe —> recipeBook -> Object.prototype —> null

The object recipe doesn’t own the getRecipeName property. But by setting recipeBook as its prototype, we have delegated the job of printing the name to the recipeBook instance. This is called the prototypal delegation.

Now, let say you also want recipe to delegate the task of orderRecipe() to another instance order. You can do this by extending the prototype chain like:

const order = {
  orderRecipe() {
    console.log(`${this.name} ordered!`);
  },
};

// Extending the prototype chain
Object.setPrototypeOf(recipeBook, order);

recipe.orderRecipe(); // prints 'Garlic Naan ordered!'

The prototype chain will extend to this:

recipe —> recipeBook —> order —> Object.prototype —> null

Now, I think it should be easy to relate why you are able to invoke recipe.hasOwnProperty() even though none of the object literals we declared owned hasOwnProperty. This is because all object literals implicitly inherit from Object.prototype, which means the hasOwnProptery() task has been delegated to Object.protoype.

Here’s the complete code example:

const recipe = { name: "Garlic Naan" };
const recipeBook = {
  getRecipeName() {
    console.log(this.name);
  },
};

// Set 'recipeBook' as the prototype of 'recipe'
Object.setPrototypeOf(recipe, recipeBook);

const order = {
  orderRecipe() {
    console.log(`${this.name} ordered!`);
  },
};

// Extending the prototype chain
Object.setPrototypeOf(recipeBook, order);

// Prototypal delegation in action
recipe.getRecipeName(); // prints 'Garlic Naan'
recipe.orderRecipe(); // prints 'Garlic Naan ordered!'
recipe.hasOwnProperty("name"); //true

Constructor function and the new keyword

Before I leave you with this delegation concept, I also want to talk about constructor functions and why do you need to use the new operator when creating instances. I hope with the prototype concept aside it should be easy to demystify their existence.

Every function (except fat arrow) in JavaScript has a property called prototype which is just a plain object with constructor property. This is different from the internal [[Prototype]] relationship.

Let’s revisit the previous recipe example and see how can you establish the same prototypal relationship using the constructor function.

// Constructor function 'Recipe'
function Recipe(name) {
  this.name;
}

Recipe.hasOwnProperty("prototype"); // true
Recipe.prototype.constructor === Recipe; // true

Visually it will look similar to the diagram below:

Constructor function

The property (prototype) is special because when you invoke Recipe() using the new operator, the new operator uses Recipe.prototype as a prototype for instances it creates. Once the instance is created, the new operator passes that instance as this internally as one of the parameters to Recipe().

const recipe = new Recipe('Garlic Naan');

Recipe instance delegates the task to its prototype

Now, it should be clear that why do we need to add properties to Recipe.prototype: they become available on all the Recipe instances via prototypal delegation.

_proto_ is a pseudo property for accessing the prototype of an object

// Adding properties to 'Recipe.prototype' will make them 
// available on all `Recipe` instances. 
Recipe.prototype.getRecipeName = function () {
  console.log(this.name);
}; 

Adding properties to prototype

Similarly, we can extend the chain and delegate the task of ordering the recipe to another object by setting it as a Recipe.prototype prototype.

// Order constructor
function Order() {}

Order.prototype.recipeOrder = {
  recipeOrder() {
    console.log(`${this.name} ordered!`);
  },
};

// Setting up the delegation aka Prototypal inheritance
Object.setPrototypeOf(Recipe.prototype, Order.prototype);

recipe.orderRecipe(); // prints 'Garlic Naan ordered!'

The complete code example using Function constructor looks like:

// Constructor function 'Recipe'
function Recipe(name){this.name}

Recipe.hasOwnProperty('prototype'); // true
Recipe.prototype.constructor === Recipe; // true

const recipe = new Recipe('Garlic Naan');

Recipe.prototype.getName = function () {
  console.log(this.name);
};

// Order constructor
function Order() {}

Order.prototype.recipeOrder = {
  recipeOrder() {
    console.log(`${this.name} ordered!`);
  },
};

// Setting up the delegation aka Prototypal inheritance
Object.setPrototypeOf(Recipe.prototype, Order.prototype);

// Prototypal delegation in action
recipe.getRecipeName(); // prints 'Garlic Naan'
recipe.orderRecipe(); // prints 'Garlic Naan ordered!'
recipe.hasOwnProperty("name"); //true

Here’s how the final prototype chain looks like:

Final prototypal delegation in action


Closing thoughts

Prototypes in JavaScript may seem daunting to begin with, but I hope this article has eased your learning path. Understanding the foundation of JavaScript is the key to become a good developer. If you want to explore more about the prototype chain, then I highly recommend reading this chapter by Dr. Axel. Thank you for reading 😍.


Amandeep Singh

Written by Amandeep Singh. Developer @  Avarni  Sydney. Tech enthusiast and a pragmatic programmer.