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