DEV Community

Cover image for An Easy Explanation to Prototypal Delegation in JavaScript
Amandeep Singh
Amandeep Singh

Posted on • Updated on

An Easy Explanation to Prototypal Delegation in JavaScript

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
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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!'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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');
Enter fullscreen mode Exit fullscreen mode

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);
}; 
Enter fullscreen mode Exit fullscreen mode

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!'
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

Here's how the final prototype chain looks like:
Alt Text


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 😍.

Oldest comments (1)

Collapse
 
frondor profile image
Federico Vázquez • Edited

My 0.50c about the constructor section: arrow functions don't have prototype. Neither bound functions, but the later can be constructables, unlike arrow functions. It's because of the [[Constructor]] internal method.
For instance: var fn = (function () {}).bind(Array); new fn() works although the result of the bind call is an "exotic object" without prototype. Now, the following example throws an error, because arrow functions lacks said internal method: var fn = (() => {}).bind(Array); new fn()