All about JavaScript Prototypes

A lifelong learner. Love to travel. Listen to music.
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 Prototypes
The simplest way to create a prototype is by using the __proto__,
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);
But while creating our 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 Chain 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 Chain 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 Chain 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,
callapplybind__proto__, this point to the base object of JavascriptMany 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.




