All about JavaScript Prototypes

All about JavaScript Prototypes

The class keyword in Javascript is syntactic sugar. Internally it uses prototypal inheritance. Prototypal inheritance is like Java or Python classical inheritance. It allows an object to access another object's methods and properties.

It helps

  • Implement inheritance

  • Avoid repeating functionalities

  • Making innovative programming paradigms

Both array and function are objects in Javascript. So any object, array, and function can use the property of the parent prototyped chain object properties and methods.

Quick Example

Let's run the following code,

const parent = {
    foo: () => {
        console.log('I am parent!');
    },
    bar: 'Hello World!'
};

const child = {
    foo: () => {
        console.log('I am child!');
    }
}

child.__proto__ = parent;

child.foo();
console.log(child.bar);

We will see the output,

I am child!
Hello World!

Here, even if the child object does not have the bar property, it will inherit the property from the parent object using the prototype chain.

Overview of Prototypes

Example 1: Object Prototypes Chain

Let's declare an object

const obj = {};

We will find the base object if we check the prototype chain.

obj.__proto__

Example 2: Array Prototypes Chain

const array = [];

Here we created an array. This array object is created by the constructor of an object. We can access these constructors and other array methods like concat, fill, find, push, pop etc using the following,

array.__proto__;

Using the prototypes chain we can access the base object, using the following,

array.__proto__.__proto__;

From this base object, we can access the base object properties and methods like, constructor, hasOwnProperty, toString, valueOf etc.

Now we can use the base object method using the prototypes chain. For example, to use the toString method from the base object, we can write

array.toString();

Since our array is empty, it will return an empty string.

Example 3: Function Prototypes Chain

Let's create a method

function a() {}

Using the prototypes chain, we can observe the native function. The native function is the one, that created all other functions.

a.__proto__;

Using the prototypes chain, if we go one step closer, we will find the base object.

a.__proto__.__proto__;

Creating our own Prototypes

Before creating our own prototypes, we should be aware of the,

  • We should never use the keyword __proto__ because that impacts execution performance.

  • The keyword is named intentionally with four underscores, so no one accidentally uses it.

To create the prototypes-chain we can use the Object.create() function.

const human = {
  mortal: true
};
const socrates = Object.create(human);
socrates.age = 80;
console.log(`Age of socrates: ${socrates.age}`);
console.log(
  `Is human a prototypes of socrates: ${human.isPrototypeOf(socrates)}`
);

This will give us the following output,

Age of socrates: 80
Is human a prototypes of socrates: true

Prototypes In Action

Moment of truth. We will see how we can get benefits from the prototypes.

Let's create two characters,

const dragon = {
  name: 'Tanya',
  fire: true,
  fight() {
    return 5;
  },
  sing() {
    return `I am ${this.name}, the breather of fire`;
  }
};

const lizard = {
  name: 'Kiki',
  fight() {
    return 1;
  }
};

Now we want to use the sing() method of for lizard. But lizard does not have the sing() method. In this case, we have to use the dragon object sing() method.

To use the dragon objects method, we can use the bind property.

const singLizard = dragon.sing.bind(lizard);
singLizard();

This will give the output

I am Kiki, the breather of fire

Now if we update the dragon objects sing() method, that, to sing, the fire property is will be required, then the new dragon object will be,

const dragon = {
  name: 'Tanya',
  fire: true,
  fight() {
    return 5;
  },
  sing() {
    if (this.fire) {
      return `I am ${this.name}, the breather of fire`;
    }
  }
};

In this case, if we call singLizard() the sing method will not return the statement.

To return the statement, we have to bind the fire property for the lizard also from the dragon object.

To resolve this issue, instead of binding, we can use the prototypes chain.

lizard.__proto__ = dragon;
lizard.sing();

In this case, when the sing() method will not be found in the lizard object, it will check the prototypes chain and execute the sing() method from there.

After using the prototypes chain, if we invoke the fight() method for the lizard object,

lizard.__proto__ = dragon;
lizard.fight();

This will return

1

Because this fight method already exists in the lizard object. So it will not go to the prototypes-chain for fight() method of dragon object.

We can if the dragon is a prototypes of lizard by the following,

dragon.isPrototypeOf(lizard);

This will be true. And this isPrototypeOf came from the base object, also using the prototype chain.

Properties of Prototypes

Property 1: Prototype does not copy properties, it is inherited

We will see for object, array and function examples that all the prototypes chain just inherited the property from the prototyped object.

Prototypes Chian With Object

Using the previous example,

const dragon = {
  name: 'Tanya',
  fire: true,
  fight() {
    return 5;
  },
  sing() {
    return `I am ${this.name}, the breather of fire`;
  }
};

const lizard = {
  name: 'Kiki',
  fight() {
    return 1;
  }
};

lizard.__proto__ = dragon;

for (let prop in lizard) {
  console.log(
    `Is ${prop} lizards own property: ${lizard.hasOwnProperty(prop)}`
  );
}

This will give us output

Is name lizards own property: true
Is fight lizards own property: true
Is fire lizards own property: false
Is sing lizards own property: false

So we can see, only name and fight are the lizard own property. Both fire and sing properties have come from the prototypes-chain.

Since it is not copying the properties, this is helpful. For example, if we use sing method of dragon object in multiple places, we can use one single instance. We are not repeating ourselves and saving memory.

Prototypes Chian With Array

let's define an array.

const myArr = [];

We know the javascript array has a property called map. Let's check

myArr.hasOwnProperty('map');

This will return false. Because the map come through the prototypes-chain. When we create an array, it is created by the Array object. Using prototypes chain we can access the Array and check the map property existence.

myArr.__proto__.hasOwnProperty('map');

This should return true.

Prototypes Chian With Function

Let's create a function

function myMethod() {}

We know function are a special type of object in Javascript.

So the following statement should return true.

myMethod.hasOwnProperty('call');

But this will return false.

Because, these call, bind, apply methods appear in a method through the prototypes-chain.

The __proto__ of the myMethod linked to the native base function prototypes object.

That base function prototypes object contains all the following,

  • call

  • apply

  • bind

  • __proto__, this point to the base object of Javascript

  • Many more ...

myMethod.___proto__.hasOwnProperty('call');

This will return true.

And this __proto__ of the base function prototypes chain to the base object prototype property.

Base object __proto__ of the prototypes property point to the null.

Property 2: Only the function and base object have the prototype object

Let's create an object and a function and check if there is prototype object.

const obj = {};
function myFunc() {}

console.log(obj.hasOwnProperty('prototype'));
console.log(myFunc.hasOwnProperty('prototype'));

This will return

false
true

Now we know the base object of Javascript Object has a property property.

Object.hasOwnProperty('prototype');

This will return true.

Actually, the Object of the javascript is a function, not an object.

Usage of Prototypes in Daily Programming

To me, this is the most interesting section. We will go through some real-world scenarios where we can implement the concept of prototypes.

Add a new functionality in Date object

Let's add a new function named lastYear of the Date object, that will return the 1 year earlier than the current year.

Date.prototype.lastYear = function () {
  return this.getFullYear() - 1;
};
new Date('1900-10-10').lastYear();

Here we use the function keyword instead of the arrow method to use dynamic-scope instead of lexical-scope.

When using the function keyword, the this context is the new Date.

But with the arrow method the this context is the arrow method itself, given below,

Date.prototype.lastYear = () => {
  return this.getFullYear() - 1;
};

new Date('1900-10-10').lastYear();

It will throw an error of,

TypeError: this.getFullYear is not a function

Manipulate the map functionality of the array

We will add the text manipulated before each value of the array,

Array.prototype.map = function (args) {
  const newArray = [];
  this.forEach(val => newArray.push(`manipulated ${val}`));
  return newArray;
};

console.log([1, 2, 3].map());

This will give us output

[ 'manipulated 1', 'manipulated 2', 'manipulated 3' ]

Final Thoughts

Prototypes are one of the core foundations of JavaScript. Let me know for any queries. Also, you can share your thoughts on how you utilize the prototypes in daily programming.

References: JavaScript: The Advanced Concepts by Andrei Neagoie.