Skip to content

Prototypes and Prototype Chain

Posted on:June 24, 2023 at 08:23 PM

The prototype exists in all objects as a built-in property. This property helps to share functions between object instances…

prototypes-and-prototype-chain Photo by Blake Connally Unsplash

Table of Contents

Open Table of Contents

Functional Instantiation

Functional instantiation means creating objects with functions. These functions are called constructor functions.

// Constructor function
function Todo(title, description, creationDate){
 let todo = {};
 
 todo.title = title;
 todo.description = description;
 todo.creationDate = creationDate;
 todo.archiveLocation = "";

 todo.moveToArchive = function (archiveLocation) {
  console.log(`${this.title} moved to archive.`);
  this.archiveLocation = archiveLocation;
 }

 return todo; // Return created object.
}

const myTodo = Todo("myTodo-title", "myTodo-description", new Date());

But the above example contains a problem. This problem is overusing memory because we are creating a new moveToArchive function every time an object is created and we are not sharing this function among the object instances. The solution is …

Functional Instantiation with Shared Methods

const todoMethods = {
 moveToArchive(archiveLocation) {
  console.log(`${this.title} moved to archive.`);
  this.archiveLocation = archiveLocation;
 }
};

function Todo(title, description, creationDate){
   let todo = {};
   
   todo.title = title;
   todo.description = description;
   todo.creationDate = creationDate;
   todo.archiveLocation = "";
  
   // Share the function between instances.
   todo.moveToArchive = todoMethods.moveToArchive;
  
   return todo;
}

const myTodo = Todo("myTodo-title", "myTodo-description", new Date());

Now we are sharing the moveToArchive function between object instances but when we plan to add a function to the todoMethods we should also change the constructor function Todo. With the help of the Object.create function we can copy the properties of todoMethods implicitly.

//...
function Todo(title, description, creationDate){
   let todo = Object.create(todoMethods);

   todo.title = title;
   todo.description = description;
   todo.creationDate = creationDate;
   todo.archiveLocation = "";

   return todo;
}
// ...

Now when we add a function to the todoMethods we do not have to change the Todo constructor function.

Prototypal Instantiation

In Javascript, every object has a built-in property the prototype. We can use this property rather than managing todoMethods in another object.

Array functions like concat, filter, and find are inherited using the prototype property. (Array.prototype.concat, Array.prototype.filter)

function Todo(title, description, creationDate){
   // Now we are inheriting functions from prototype
   let todo = Object.create(Todo.prototype);

   todo.title = title;
   todo.description = description;
   todo.creationDate = creationDate;
   todo.archiveLocation = "";
  
   return todo;
}

// Put the function into prototype.
Todo.prototype.moveToArchive = function(archiveLocation) {
 console.log(`${this.title} moved to archive.`);
 this.archiveLocation = archiveLocation;
};

const myTodo = Todo("myTodo-title", "myTodo-description", new Date());
myTodo.moveToArchive();

What About Static Methods?

Static methods are defined directly as object property due to they do not require the instance and they are not overridden.

Todo.prototype.moveToArchive = function()...
Todo.staticMethod = function()...

We can use the hasOwnProperty function. This function returns true if the property exists in the object itself(not in its prototype) otherwise returns false.

//...
const myTodo = Todo("myTodo-title", "myTodo-description", new Date());
console.log(myTodo.hasOwnProperty('description')); // true
console.log(myTodo.hasOwnProperty('staticMethod')); // false
console.log(myTodo.hasOwnProperty('moveToArchive')); // true ->
// Because we are creating the todo with Object.create(Todo.prototype)

What is the ‘this’ keyword and how does inheritance happen with the prototypes?

When we call a property of an object, JS checks that the object has its own property (like above). If the object has the corresponding property, evaluation finishes otherwise JS looks to the object’s prototype. (i.e. the parent object)

Let’s take a look at the example from MDN:

const parent = {
  value: 2,
  method() {
    return this.value + 1;
  },
};

// In this case `this` keyword refers to the 'parent' object.
console.log(parent.method()); // 3

const child = {
  __proto__: parent, // We are setting the object's prototype.
};

// In this case `this` keyword refers to the `child` object.
// `child` object neither has property `method` and `value`
// These are resolved using the child's prototype which is 
// the `parent` object.
console.log(child.method()); // 3

// Now the `child` has own property `value`
child.value = 10;

// this.value = 10
console.log(child.method()); // 11

// Also we can override the `method`
child.method = function () {
  return this.value + 10;
}

console.log(child.method()); // 20

console.log(child.__proto__.method()); // 3
// Because now the `this` keyword refers to `child.__proto__`, the
// `parent` object.

console.log(child.__proto__.method.apply(child)); // 11
// with the `apply` function we are setting
// the `this` property to `child`. But the `method` is coming
// from the `parent` object.

console.log(child.method.apply(parent)); // 12
// the child.method is coming from the `child` which is overridden
// at line 28, and we are setting the `this` property to 
// the `parent` object so the `value` property is equal to 2.
// so the result is 12.

Pseudoclassical Instantiation — The ‘new’ Keyword

With the new keyword we can omit the call to function Object.create because the new keyword does this automatically.

function Todo(title, description, dateCreated) {
  // Below statement automatically included 
  // when we call new Todo(...)
  // const this = Object.create(Todo.prototype)

  this.name = name;
  this.energy = energy;

  // Below statement automatically included 
  // when we call new Todo(...)
  // return this ;
}

const todo = new Todo("myTodo-title", "myTodo-description", new Date());

Prototype Chains

Every object has a field prototype but think about it the prototype field itself is an object so also prototype field has the prototype property. This continues like a chain until one of the prototypes is equal to the null. The null value terminates the prototype chain.

const o = {
  a: 1,
  b: 2,
  __proto__: { 
    b: 3,
    c: 4,
  },
};

// o = {a: 1, b: 2}
// o.[[Prototype]] = {b:3, c:4}
// o.[[Prototype]].[[Prototype]] = Object.prototype
// o.[[Prototype]].[[Prototype]].[[Prototype]] = null | End of the chain

console.log(Object.prototype.__proto__); // null

The Complete Example

function Animal(name){
 this.name = name;
}

Animal.prototype.eat = function(){
  console.log(`${this.name} is eating.`);
}

function Lion(name, age) {
  Animal.call(this, name); // We are calling the base constructor.
  this.age = age;
}
Lion.prototype.walk = function() {
  console.log(`${this.name} is walking`);
}
Lion.roar = function () {
  console.log("Static function is called.");
}

Object.setPrototypeOf(Lion.prototype, Animal.prototype); // Inheritance

const animal = new Animal("An Animal");

animal.eat(); // An Animal is eating.

const lion = new Lion("The Lion", 8); // The Lion is eating.

lion.eat();
lion.walk(); // The Lion is walking

// animal.walk(); Error animal.walk is not a function
// lion.roar(); Error lion.roar is not a function

Lion.roar(); // Static function is called.

How to write this example using modern Javascript (ES6)?

class Animal {
  constructor(name){
    this.name = name;
  } 

  eat(){
   console.log(`${this.name} is eating.`);
  }
};

class Lion extends Animal {
  constructor(name, age){
    super(name);
    this.age = age;
  }
  
  walk(){
 console.log(`${this.name} is walking`);
  }

  static roar(){
   console.log("Static function is called.");
  }
};

const animal = new Animal("An Animal");

animal.eat(); // An Animal is eating.

const lion = new Lion("The Lion", 8); // The Lion is eating.

lion.eat();
lion.walk(); // The Lion is walking

// animal.walk(); Error animal.walk is not a function
// lion.roar(); Error lion.roar is not a function

Lion.roar(); // Static function is called.

Thanks for reading…


See Also