Crack 'this' concept of Javascript.
SadkiratSingh
Posted on June 11, 2021
Introduction
Javascript in itself is a mixture of functional programming paradigm and object oriented programming paradigm. this is one of the fundamental ingredients of object oriented programming.
this is one of the built-in parameters in javascript which is implicity available inside a javascript function. this is passed a value by the javascript engine, behind the scenes, when the function is called. For example, it contains a reference to the object on which the function is invoked. That is why, It is also called function context.
This and Function Invocation
That was a brief introduction to this. Lets get into nitty gritty of how can we crack the value of this which is the main goal of this article.
Value of this parameter inside a function depends on the manner in which the function is invoked. Yes, there are different ways to invoke/call a function in javascript.
We can invoke a function in javascript in 4 ways:
- As a function-
assemble() /* assume that *assemble* is a simple javascript function*/
- As a method-
avengers.assemble() /*This ties the function call to an object
enabling OOPS. This is also called calling a function on an
object.*/
- As a constructor-
new Avenger() /* This is how you create new objects in
javascript: preceding function call with 'new'.*/
- Via the function's apply/call methods-
smash.call(hulk); // smash is a function here.;
smash.apply(hulk);
Lets start discussing them one by one with the help of examples.
Invocation as a function:
// all ways to invoke a function as a function.
function assemble(){
console.log(this); //outputs window object
}
assemble();
let assemble = function(){
console.log(this); // outputs window object
}
assemble();
(function(){
console.log(this); // outputs window object
})();
In each case we will observe that console outputs a window object. Hence, when function is invoked as a function this parameter inside the function starts referring to window object which is inbuilt javascript global object in browser. However if we run our code in strict mode this in such a case becomes undefined.
function assemble(){
'use strict';
console.log(this); //outputs undefined
}
Invocation as a method:
When a function is assigned to a property of an object, it becomes a method.
let avengers = {
assemble:function(){
console.log(this); //outputs avengers object
}
};
avengers.assemble(); // assemble is called upon avangers.
In this example, assemble is a method of avengers object. We observe that this parameter inside assemble function holds a reference to the avengers object when assemble is called upon avengers object. This gives us a sense of how OOPS is being implemented in javascript.
this parameter inside a function will point to the object in which the function is defined if the function is called upon that object. When a function is called upon an object in which it is defined, we say that the function has been invoked as a method.
Invocation as a constructor:
A simple function in javascript becomes a constructor when it is invoked in a specific way in javascript. Lets dive straight into code to know this specific way.
function Avenger(name,weapon){
this.name = name;
this.weapon = weapon;
}
let Thor = new Avenger('Thor','Mjolnir');
See here Avenger itself is a simple javascript function. When it is invoked, it is preceded by an inbuilt javascript keyword new. This type of invocation converts our simple function into a constructor. So we say, we invoked our function as a constructor.
When any function is invoked as a constructor, three things happen:
- a new empty javascript object is created in memory.
- Inside the constructor(Avenger function), this parameter starts referring to this newly created object.
- After the execution of constructor finishes, the newly constructed object is returned as new operator's value even though no return statement is there in constructor. Therefore, Thor variable starts referrring to our new object as shown in figure below.
Invocation with apply and call methods:
We know, in javascript, functions are first class objects. Therefore, properties can be assigned to them. call and apply are built-in methods available on any function in javascript. Both functions work in almost similar way.
Lets now dive straight into code again to understand mechanism of this here.
function setQuality(...args){
this.qualities = args;
}
let Thor = {name : 'Thor'};
let Captain = {name : 'Steve'};
setQuality.call(Captain,'diligent','persistent','motivated');
setQuality.apply(Thor,['loyal','heroic','bold'])
console.log(Thor.qualities); // outputs ['loyal','heroic','bold']
console.log(Captain.qualities); // outputs ['diligent','persistent','motivated']
In above example, we have function setQuality. This function takes a list of arguments which is represented by args parameter. It is a special parameter called as rest parameter. You can read more about rest parameter here. For the time being, consider args to be a pure javascript array which contains all arguments sent to the function.
Now, when setQuality.call method is invoked, setQuality itself is invoked. First argument to setQuality.call will become the value of this parameter inside setQuality. Rest of the arguments passed to setQuality.call are the arguments with which setQuality is invoked and will go inside array represented by args parameter of setQuality.
Similarly, when setQuality.apply method is invoked, setQuality itself is invoked. First argument to setQuality.apply will become the value of this parameter inside setQuality. Second argument to setQuality.apply is an array. Values inside this array just represent the arguments with which setQuality is invoked and will go inside array represented by args parameter of setQuality.
You can read more about call method here.
You can read more about apply method here.
this in Event Handlers
To understand how the value of this parameter is decided inside a function which acts as an event handler, let's take an example:
<button id='demo'>Click!</button>
<script>
function Avenger(name,weapon){
this.name = name;
this.weapon = weapon;
this.maxHealth = 100;
this.displayMaxHealth = function(){
console.log(this.maxHealth);// outputs undefined.
}
}
let Thor = new Avenger('Thor','Mjolnir');
let btn = document.getElementById('demo');
btn.addEventListener('click',Thor.displayMaxHealth);
</script>
It is a little surprising that instead of 100, undefined is output on console when we click the button. The reason behind this goes like this.
Inside the addEventListener method we passed a reference to our displayMaxHealth method which is defined inside Avenger function. displayMaxHealth(event handler) will be called by the event handling system of the browser when we click the button inside the DOM with id 'demo'. Therefore, value of this which will be passed to this function at the time of its invocation is also decided by the event handling system of the browser. It sets this to the button element instead of Thor object.
Since button object does not have any variable with name maxHealth, therefore undefined is output on the console.
Therefore, this parameter in case of event handlers refers to DOM element that was the target of the event and is handled by event handling system of browser.
Now if we want to output the correct value of 100, Javascript provides us with some ways.
One of the ways can be to use the built-in bind method. This method, like apply and call, is defined on every function in javascript. This method, when called on a function, creates and returns a new function. This new function has some special characteristics.
- The new function has the same body as the function on which bind was called.
- Also, the value of this parameter inside the new function will always be bound to the value passed as first argument to bind method, regardless of the manner in which we now invoke the new function.
This will become clear when we modify above example.
<button id='demo'>Click!</button>
<script>
function Avenger(name,weapon){
this.name = name;
this.weapon = weapon;
this.maxHealth = 100;
this.displayMaxHealth = function(){
console.log(this.maxHealth);// outputs 100
}
}
let Thor = new Avenger('Thor','Mjolnir');
let btn = document.getElementById('demo');
btn.addEventListener('click',Thor.displayMaxHealth.bind(Thor));
</script>
The second way can be to use the arrow functions. This is because value of this parameter in case of arrow functions is decided in a specific way which I am going to discuss in the next section.
this in Arrow functions.
Arrow function was introduced in ES6 and is really a cool feature in javascript.
Arrow functions don't have their own this value which means javascript engine does not pass a value to the this parameter of arrow functions at the time of their invocation. Then, how is the value of
this decided?
The value of this parameter in case of arrow functions is decided at the time of their declaration. Value of this is decided by the context in which arrow functions are declared. This will become clear with examples.
function Avenger(name,weapon){
this.name = name;
this.weapon = weapon;
this.maxHealth = 100;
this.reduceHealth = () =>{
this.maxHealth-=Math.floor(((Math.random())*100)+1);
}
}
let Thor = new Avenger('Thor','Mjolnir');
Thor.reduceHealth();
console.log(Thor.maxHealth);// will output a numeric value.
reduceHealth function is an arrow function declared inside constructor function Avenger. So, we say reduceHealth is declared inside the context of Avenger function. Since Avenger is a function, it's context is called a functional context. Inside Avenger function, value of this refers to a newly constructed object. Now, we can say that value of this parameter inside Avenger function is passed on to this parameter inside reduceHealth function as shown in above example.
Let's take another example.
let Captain ={
name:'Steve',
weapon:'Shield',
maxHealth:100,
displayMaxHealth : () =>{
console.log(this.maxHealth);
}
}
Captain.displayMaxHealth(); // console outputs undefined.
Why did console output 'undefined' in this example? This is because of the way value of this parameter is determined inside an arrow function.
In this example, displayMaxHealth is a method on Captain object. Unlike previous example, it is not declared inside a functional context. Captain object itself is declared inside global context i.e. outside of all functions. Therefore, displayMaxHealth method, declared inside Captain object, is said to be declared inside a global context.
Note: global context, global level(outside all functions) are synonymous terms.
What is the value of this inside global context? Lets see.
console.log(this); //this outputs global 'window' object.
Therefore, since displayMaxHealth is lying inside global context and value of this inside global context is window object, hence value of this parameter inside global context is passed on to the this parameter inside displayMaxHealth function.
Now, this inside displayMaxHealth points to window object and the window object does not contain any variable with the name maxHealth. This explains why we got undefined in the output in above example.
Note: Inside global context, if we are using strict mode, value of this will be undefined.
Conclusion
this concept is one of the most fundamental and important concepts in Javascript. Sometimes it becomes a bit tricky to grasp the nitty gritty of the concept. However, once you fully understand this concept, it is going to help you a lot in the long run.
Hopefully these explanations helped you really understand the concept of this in JavaScript!.
Posted on June 11, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.