Let us try to get to the very basic of objects in JavaScript before trying to understand anything of prototypal inheritance.To make a object as it turns out there are 4 different approaches.We will try to understand each and everyone of them one by one and in the process see the need of _proto_ and prototypal inheritence achieved through _proto_.
1)The curly braces approach
This is the most popular one of all the 4 approaches and really easy to understand.A very simple example would be:
let student1 = {
name: 'Sourav',
age: 21,
incrementAge: function () {
student1.age++;
},
play: function () {
console.log("playing");
}
}
student1.incrementAge();
student1.play();
The dry run of the above code will be as follows:
1)We define student1 as a label to a object having name and age as properties and 2 function definition incrementAge and play in the global execution context.
2)Then we hit the next line student1.incrementAge().That is we are calling student1.incrementAge.So, the global thread will look for student1 in the global memory and it will be successfull and then it will look what is after the dot notation and find it to be incrementAge which is a function.Now global thread will look inside the object and try to find in it a function definition of incrementAge which will also be successfull.So the incrementAge function is added to the top of the call stack.
3)Now, a new execution context the local execution context will be created with local memory and local thread.In this context, the function incrementAge will be run.Now we are inside the incrementAge function where we get student1.age and add 1 to it.Once this is done, the incrementAge function is popped off the call stack.
4)Similarly the call to play is added to the call stack and then popped off in the same manner as well.
So, we see that using this curly brace approach is very easy to implement and very intuitive and we also get the main benefit of objects i.e. to bundle some functionalities and some properties related to that object.
But every coin has two parts, while the object making is very easy but it has 2 very severe disability.They are:
1)Let us imagine a college and suppose we are to make 1000 students and represent them as objects.We can clearly see the amount of work we have to do to get the job done.We need to manually type in every student's name and age and write the same two functions on every student object.Please see that i have written the same two functions in bold just because although the name and age of different students will be different but inherently we have to write the same functions again and again thus violating the basic principles of DRY(Dont Repeat Yourself).Also the rewriting of the same function again and again will lead to memory issues as well.
2)Suppose we want to edit or add a new functionality to every student.As can be guessed there is no easy way to do this.
Thus we arrive at a new approach of creating objects
2) Using Object.create() approach
The another way of creating object we can use Object.create().But, What do Object.create actually do under the hood?
Well,as it turns out Object.create(any argument we pass here) always creates a empty object and the argument determines a special hidden bond of the object to the argument of Object.create().So, let obj1=Object.create(null)
creates a empty oject andlet obj2=Object.create(someRandomObject)
also creates a empty object but the difference is only that obj1 have the special hidden bond pointing to null but obj2 has that bond pointing to someRandomObject.
THIS VERY IMPORTANT BOND IS ACHIEVED BY A INBUILT HIDDEN PROPERTY OF OBJECTS CALLED _proto_
function studentCreator(name, score) {
let newStudent = Object.create(studentFunctions);
newStudent.name = "Sourav";
newStudent.age = 21;
return newStudent;
}
let studentFunctions = {
incrementAge: function () {
this.age++;
},
play: function () {
console.log("play");
}
}
let student1=studentCreator("Sourav",21);
student1.incrementAge();
student1.play();
Let us have a dry run of the above code
1)First Line we start at the global execution context in the global memory we define a function named studentCreator.
2)Then we define studentFunctions as a object.
3)Then we create a label student1 whose value will be determined by the output returned by studentCreator.
4)In the global thread, studentCreator is called and is pushed to the call stack and a new execution context is created where we run studentCreator with arguments Sourav and 21
5)In the local execution context name is assigned value of Sourav and age as 21.
6)Inside the studentCreator function newStudent is created as an empty object with a special hidden bond stored in the _proto_ property to studentFunctions because Object.create(studentFunction) is called.
7)The other 2 lines are just assigning name to that newStudent and age to it.
8)In the last line we return out the entire object from the function and the function is popped off the call stack.
9)This returned objects gets the label of student1.
10)Now when we call student1.incrementAge() in the next line.The interpreter looks in the global memory for student1 and finds it to be object and then moves forward to what is after the dot notation.Now, JavaScript interpreter looks for incrementAge function definition inside the student1 object.It does not find the property so what does it do?
It turns out that it will now look in the hidden _proto_ property and try to find the function.There,it is successfull in finding the incrementAge function and runs it.
10)The running of incrementAge function creates a new execution context,the local execution context for that function with local memory and local thread.The this keyword is first set to the object before the dot notation.
11)In the next line, this.age++
is run with this=student1.Thus, student1.age++ is achieved as we had planned to achieved.
12)Similarly by finding the play function _proto_ property the next line is run.
Thus, what we are achieving is that we are successfully inheriting the two functions in every object we create like this.And we don't have to rewrite or copy the functions to every object we create.
But the main benefit is that we are never storing the functions in our individual objects instead they get a reference to the function from the _proto_ not a copy of the functions so we don't have any memory issue as in the previous approach of object creation.
The next 2 approaches or methods of creating objects is just automating the above stuff and some syntactic sugar over the above methods.
Let us explore them one by one
3)Object creation using the new KeyWord
The key to understanding the new keyword operation is that we must understand first that in JavaScript functions are also objects so they also can have properties like normal objects.Every function have a special property in them called prototype which itself is an object.This prototype property is used in the execution of new keyword which we will see shortly.
1 function studentCreator(name,age){
2 this.name=name;
3 this.age=age;
4 }
5 studentCreator.prototype.incrementAge=function(){
6 this.age++;
7 }
8 studentCreator.prototype.play=function(){
9 console.log("play");
10 }
11 let student1=new studentCreator('Mrinal',22);
12 student1.incrementAge();
13 student1.play();
Let us do a dry run of this as previous codes:
1)we define a function studentCreator in the global execution context.
2)Then we access the prototype object of studentCreator and set a property incrementAge which is a function definition
3)Then we again access the prototype object of studentCreator and set a property play which is also a function definition.
4)Then in line 11 we create a label student1 still in the global execution context and set it to return of new studentCreator("Mrinal",22)
5)As a function is called so a new execution context is created with local memory and local thread.Also it is pushed to the call stack.First name parameter is set to Mrinal and age is set to 22.
6)The new keyword automates a ton of stuff.It first inserts the following code in the function studentCreator's body behind the scene:
this=Object.create(studentCreator.prototype);
The above code creates a empty object with its _proto_ refering to studentCreator.prototype.
7)In line 2 and 3 we set the name and age property to the name and age passed in the parameters which are Mrinal and 21 respectively.
8)Again the new keyword comes in and insert the following code behind the scenes
return this;
Thus the object is returned without us ever having to write the return keyword.
Thus the function is popped off the call stack.
9)Now we call student1.incrementAge().Now in the global memory we try and find student1 which is found. Now we proceed after the dot notation and find incrementAge which is called as a function.So, the interpreter tries to find it in the object methods but couldn't find it so it searches in the object's _proto_ property and find it refering to studentCreator.prototype so the interpretor searches the studentCreator.prototype property and finds the function and thus we run it.
10)So a new execution context is created as a function is called and the function is pushed to the call stack.In the first line of function this is set to the object calling that method thus, this=student1.Thus, student1.age++ is achieved again.As the function finishes it is popped off the call stack.
11)In the next line, similar to the above point we find the method in studentCreator.prototype and runs it and a new execution context is made and console.log("play")
is run.
Thus we are achieving the same thing as without using new keyword,only difference is that we are actually automating a lot of stuff and writing fewer lines of code.
The last type is using class
keyword to create object
4)Using class
keyword to create objects
class
keyword is actually just syntactical sugar over the last method we saw.Since in most OOP languages we write our shared methods in the object itself and not separately as in JavaScript so in the ES-2015 version of JavaScript we have the keyword class which makes making objects in JavaScript similar in looking to that in Java or Python.But we must understand that it only similar in looking but the functionality is vastly different than in other languages.We are still under the hood using prototypal inheritance and not other sort of magic.Only thing we are doing is writing the constructor and methods in the same place.
1 class studentCreator{
2 constructor(name,age){
3 this.name=name,
4 this.age=age
5 }
6 incrementAge(){
7 this.age++;
8 }
9 play(){
10 console.log('play');
11 }
12 }
13 let student1=new studentCreator('Max',21);
14 student1.incrementAge();
The dry run is as follows:
1)The constructor function is similar to studentCreator() function as described in the last method of creating objects.
2)The incrementAge and play are attached to the constructor's prototype object similar to what happened to studentCreator only that we were explicitly mentioning studentCreator.prototype.incrementAge
,here we are just declaring as normal methods but under the hood it is stored in the prototype object.
3)The new keyword behaves as the same way as in the previous method.
4)student1.incrementAge() also behaves in the same manner and finds the method in the prototype object of the constructor and the method is called in the normal fashion.
Thus using class
keyword has no other implication other than making our code easier to read and cleaner code.It is just syntactical sugar.
Conclusion
Thus, in this article we saw the various methods of object creation and what goes on under the hood while we are using prototypal inheritance in JavaScript.Hope,this article helped you understanding prototypal inheritance which is the core basic of OOP in JavaScript.
P.S. This article is inspired by the notes i created when i was watching JavaScript:The Hard Parts By Will Sentence in Front End Masters.
P.P.S. This is my first article so there may be many mistakes.It would be pleasure to me if you point out the mistakes so that i can correct them.
Top comments (3)
Hey when you're pasting code just type in 3 backticks then language (js) before code and 3 backticks after the code snippet. This will create a code block that looks like this.
Thnx for your suggestion..is there any way to create indentation?
Nope. Just format code before pasting within the code block. It'll look intended (according to the way you formatted )