Understanding “this” Keyword in Javascript
this keyword is one of the most misunderstood aspect of JS. In this article we will try to demystify the this keyword and gain a clear understanding of it. this binding in JS is actually determined by the call-site(the place from where the function is being called) and has nothing to do with where the function is declared. We will try to understand the call-site a bit before trying to understand this.
Call-Site
We can determine the call-site by analysing the call stack of the functions.
Let try to understand this by an example.
Suppose we have a code snippet like below
function func1() {
console.log("func1");
}
function func2() {
console.log("func2");
func1();
}
function func3() {
console.log("func3");
func2();
}
func3();
If we run the above program and see the call stack(can be seen in the below image) it would be something like global -> func3 -> func2 -> func1.
So when func3 is called it’s call-site is in the global execution context. Similarly when func2 is called it’s call-site is in func3. Similarly we can infer for func1.
Now that we have got a bit of understanding of call-site. Let’s try to understand this. To determine this we will have to understand the following rules -
Default Binding
Let take the below code to understand this scenario.
function getName() {
console.log(this.name);
}
var name = "Abc";
getName();
From the above snippet we examine the call-site and see that getName is called normally. So in this scenario this points to the global object and we will see Abc in the console(Since name property will be stored in the global object after execution).
If strict mode is in effect the global object this is set to undefined so the below snippet will lead to error.
function getName() {
"use strict";
console.log(this.name);
}
var name = "Abc";
getName();
// TypeError: Cannot read properties of undefined (reading 'name')
Implicit Binding
When the call-site has a context object then that object is used for the this.
Consider the following snippet
function getName() {
console.log(this.name);
}
var user = {
name: "Adam",
getName,
};
user.getName(); // Adam
In the above snippet the call-site uses the user object to reference the getName function. We can also think of it that during call of the getName function the user object contains the function and hence used for the binding.
If we have chain of properties then only the last level of the object property matters. We can see that in below example
function getName() {
console.log(this.name);
}
var newUser = {
name: "Aditya",
getName,
};
var user = {
name: "Adam",
newUser,
};
user.newUser.getName(); // Aditya
Now suppose we try to use the function by storing it in a variable and then invoke that variable. Then that will lead to different result. If we see the call-site then we can see that the this points to the global object hence that is used instead of user object.
function getName() {
console.log(this.name);
}
var name = "Abc";
var user = {
name: "Adam",
getName,
};
var newGetName = user.getName;
newGetName(); // Abc
Explicit Binding
When we force the function call to use a particular object for the this object we call it explicit binding. We have call,apply and bind methods for functions where we can provide the object to be used as this in first argument.
function getName() {
console.log(this.name);
}
var user = {
name: "Adam",
};
getName.call(user); // Adam
In the above snippet we are passing as the user object during calling so that would be used for the this binding.
new Binding
Whenever a function is called with a new keyword(constructor) a brand new object is created and that object is used for this for the function call.
An example of this can be seen below.
function setName(name) {
this.name = name;
}
var user = new setName("Aditya");
console.log(user.name); // Aditya
Now that we have seen all the rules, the value of this for normal function can be found by applying these in the call-site. When multiple rules are being applied then the order of preference would be new binding > explicit binding > implicit binding > default binding
Arrow Functions
For arrow function the above rules don’t apply and it takes it’s value from the enclosing lexical context’s this during function creation. Let’s try to understand this by an example.
const user = {
name: "Amit",
nameGetter() {
const getName = () => {
console.log(this.name);
};
getName();
},
};
user.nameGetter(); // Amit
Here when getName was created the value of this was the user object and hence that was used for the getName function. Suppose we declare the getName function outside the user object as shown below. Then it will result to different result as this refers to the global object here.
var name = "Abc";
const getName = () => {
console.log(this.name);
};
const user = {
name: "Amit",
getName,
};
user.getName(); // Abc
References
1 — https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this
2 — You don’t know js by Kyle Simpson