The ‘this’ keyword in JavaScript, demystified
When I first started learning JavaScript, it took me some time to understand the this
keyword in JavaScript and be able to quickly identify which object does the this
keyword point to. I found that the hardest thing about understanding the this
keyword is that you usually forget the different cases and situations that you have read about or watched people explain during one or more of the JavaScript courses or resources that you study from. After introducing the arrow functions in ES6, things got more confusing too because arrow functions deal with the this
keyword in a different way. I wanted to write this article to state what I have learned and try to explain it in a way that could help anyone who is learning JavaScript and having difficulty understanding the this
keyword as I did before.
As you may know, the environment (or the scope) in which any JavaScript line is being executed is called “The Execution Context”. The Javascript runtime maintains a stack of these execution contexts and the execution context present at the top of this stack is currently being executed. The object that this
variable refers to changes every time when the execution context is changed.
By default, the execution context is global which means that if a code is being executed as part of a simple function call then the this
variable will refer to the global object. In the case of a browser, the global object is the window
object. In a Node.js environment, for example, a special object global will be the value of this
.
For example, try the following simple function call:
function foo () {
console.log("Simple function call");
console.log(this === window);
}foo();
By calling foo()
, we will get this output:
“Simple function call”
true
This proves that this
here refers to the global
object, which is the window
object in our case.
Note that, if strict mode is enabled for any function then the value of this
will be undefined
because in strict mode global object refers to undefined
in place of the window
object.
Let’s try the following example:
function foo () {
'use strict';
console.log("Simple function call");
console.log(this === window);
}foo();
Our output, in this case, will be:
“Simple function call”
false
Let’s imagine now that we have the following constructor function:
function Person(first_name, last_name) {
this.first_name = first_name;
this.last_name = last_name;
this.displayName = function() {
console.log(`Name: ${this.first_name} ${this.last_name}`);
};
}
Let’s create some Person instances:
let john = new Person('John', 'Reid');
john.displayName();
If we try that in our console, we should get the following output:
"Name: John Reid"
What happened here? When we call new
on Person
, JavaScript will create a new object inside the Person
function and save it as this
. Then, the first_name
, last_name
and displayName
properties will be added on the newly created this
object. I used this awesome JavaScript visualizer tool from Tyler McGinnis to see how this actually looks like behind the scenes. This is what I got:
You will notice that we have a this
object created inside the execution context of Person
and that this object has the three properties first_name
, last_name
and displayName
. This tool animates the steps happening behind the scenes in an interesting way that will help you understand how the this
object is created and filled.
We have now discussed two common cases related to the this
keyword binding. There’s one more case that I wanted to mention and that could be a little confusing too. Imagine that we have this function:
function simpleFunction () {
console.log("Simple function call")
console.log(this === window);
}
We now know that if we do a simple function call like the following, the this
keyword will refer to the global object, in our case, it’s the window
object. Let’s do that:
simpleFunction();
Indeed, we get this in as our output:
“Simple function call”true
Let’s create a simple user object like the following:
let user = {
count: 10,
simpleFunction: simpleFunction,
anotherFunction: function() {
console.log(this === window);
}
}
Now, we have a simpleFunction
property referring to our simpleFunction
function. We have also added another property as a method called anotherFunction
.
If we call user.simpleFunction()
, we will get this in our console’s output:
“Simple function call”
false
Why did that happen? Because simpleFunction()
is now a property of the user
object, then the this
keyword will refer to the user
object and not the global object in this case.
If we call user.anotherFunction()
now, we should expect the same thing too. Our this
keyword will refer to the user
object. So, console.log(this === window);
should return false
and we should get this output in our console:
false
Let’s discuss one more case. What will happen if we do something like the following?
let myFunction = user.anotherFunction;myFunction();
If you try this, you should get this output:
true
But why did this happen? In this case, we do a simple function call. As we already know by now, if a method is invoked as a simple function, then the this
keyword will refer to the global object which is in our case equals to the window
object, and hence console.log(this === window);
will print true
.
Let’s see another example:
var john = {
name: 'john',
yearOfBirth: 1990,
calculateAge: function() {
console.log(this);
console.log(2016 - this.yearOfBirth); function innerFunction() {
console.log(this);
}
innerFunction();
}
}
What will happen now if we call john.calculateAge()
? We should get something similar to this:
{name: "john", yearOfBirth: 1990, calculateAge: ƒ}
26
Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
Inside the calculateAge
function, this
refers to the calling ‘john’ object. But inside innerFunction
function, this this
refers to the global object which in this case is the window
object. Some people see that as a bug in JS, but the rule says that whenever we do a regular function call, then this
will refer to the global object.
What I learned also is that a function in JavaScript is also a special type of object. Every function has a call
, bind
and apply
methods. These methods can be used to set a custom value of this
to the execution context of the function. Let’s see the following example:
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName; this.displayName = function() {
console.log(`Name: ${this.firstName} ${this.lastName}`);
}
}
And let’s create two new persons:
let person = new Person("John", "Reed");
let person2 = new Person("Paul", "Adams");
And call:
person.displayName();
person2.displayName();
This should print:
Name: John Reed
Name: Paul Adams
Now, let’s use call
on person
object:
person.displayName.call(person2);
What we are doing here is setting the value of this
to be person2
object. So, this should print:
Name: Paul Adams
The same also will happen if we use apply
:
person.displayName.apply([person2]);
We will get:
Name: Paul Adams
The only difference between call
and apply
methods is the way arguments are passed. In the case of apply
, the second argument is an array of arguments where in case of call
method, arguments are passed individually.
Let’s also try doing the same with the bind
method. bind
returns a new method with this
referring to the first argument passed. For example:
let person2Display = person.displayName.bind(person2);
If we now call person2Display
, we should get Name: Paul Adams
too.
Arrow Functions
As part of ES6, there is a new way introduced to define a function. It looks like the following:
let displayName = (firstName, lastName) => {
console.log(Name: ${firstName} ${lastName});
};
Unlike normal functions, arrow functions don’t get their own this
keyword. They simply use the this
keyword of the function they are written in. They have a lexical this
variable.
Let’s see an example of something that we used to do in ES5:
var box = {
color: 'green', // 1
position: 1, // 2
clickMe: function() { // 3
document.querySelector('body').addEventListener('click', function() {
var str = 'This is box number ' + this.position + ' and it is ' + this.color; // 4
alert(str);
});
}
}
If we call:
box.clickMe();
An alert will be displayed with the content: ‘This is box number undefined and it is undefined'.
Let’s analyze what’s happening here step by step. If you look at the above example, I have added some comments // 1
, // 2
and // 3
. At lines // 1
and// 2
, our this
keyword has access to color
and position
properties because it refers to box
object.
Inside clickMe
method also, our this
has access to color
and position
properties because it’s still referring to box
object. But, clickMe
method defines a callback function for querySelector
, and the callback function now is a regular function call so our this
now will refer to the global object which in this case is the body
object. Of course, position
and color
are not defined in the body
object, so that’s why their values will be undefined
and we get an alert displaying ‘This is box number undefined and it is undefined’.
We can fix this issue using an ES5 approach, by doing something like the following:
var box = {
color: 'green',
position: 1,
clickMe: function() {
var self = this;
document.querySelector('body').addEventListener('click', function() {
var str = 'This is box number ' + self.position + ' and it is ' + self.color;
alert(str);
});
}
}
Adding var self = this
is a workaround to make the closure function use the value of the this
keyword that refers to the box
object. We just need to use the new self
variable inside the callback function now.
If we call:
box.clickMe();
We should get an alert displaying: ‘This is box number 1 and it is green’.
How can the use of arrow functions help us in cases like these? Let’s see the following example that’s based on the last one we have discussed. We will replace the callback function of the click event listener inside clickMe
with an arrow function:
var box = {
color: 'green',
position: 1,
clickMe: function() {
document.querySelector('body').addEventListener('click', () => {
var str = 'This is box number ' + this.position + ' and it is ' + this.color;
alert(str);
});
}
}
The amazing thing about arrow functions is that they share the lexicalthis
keyword of their surroundings. So, in our example here it shares the this
keyword with its outer function. This this
keyword for the outer function refers to the box
object, so this.position
and this.color
will have the correct values of green
and 1
.
Let’s have one more example:
var box = {
color: 'green',
position: 1,
clickMe: () => {
document.querySelector('body').addEventListener('click', () => {
var str = 'This is box number ' + this.position + ' and it is ' + this.color;
alert(str);
});
}
}
Oh! Now, we got an alert saying: ‘This is box number undefined and it is undefined’. What happened here?
The this
keyword of the click
event listener’s closure shares the value of thethis
keyword of its surroundings. Its surroundings in this case is the arrow functionclickMe
. The this
keyword of the clickMe
arrow function refers to the global object, in this case the window
object. So, this.position
and this.color
will be undefined because our window
object does not know anything about the position
or the color
properties.
I want to show you one more example that’s a common case with the map
function which can be helpful in many situations. We defined a Person
constructor method above:
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName; this.displayName = function() {
console.log(`Name: ${this.firstName} ${this.lastName}`);
}
}
Let’s add a new method called myFriends
to Person
's prototype:
Person.prototype.myFriends = function(friends) {
var arr = friends.map(function(friend) {
return this.firstName + ' is friends with ' + friend;
});
console.log(arr);
}
Let’s create a new person:
let john = new Person("John", "Watson");
If we call john.myFriends(["Emma", "Tom"])
, we will get this output:
["undefined is friends with Emma", "undefined is friends with Tom"]
This is very close to the examples we have discussed above. myFriends
function will have a this
keyword in its body that is referring to the calling object. But, the closure inside the map
function is a normal function call, so the this
keyword inside the closure of the map
function will refer to the global object, in this case it’s the window
object which makes the value of this.firstName
undefined. Now we know how to fix that! we have at least three ways to fix this situation:
- By Assigning
this
insidemyFriends
function body to another variable, calledself
for example and using it inside themap
function’s closure:
Person.prototype.myFriends = function(friends) {
// 'this' keyword maps to the calling object
var self = this;
var arr = friends.map(function(friend) {
// 'this' keyword maps to the global object
// here, 'this.firstName' is undefined.
return self.firstName + ' is friends with ' + friend;
});
console.log(arr);
}
2. By using bind
on the map
function’s closure:
Person.prototype.myFriends = function(friends) {
// 'this' keyword maps to the calling object
var arr = friends.map(function(friend) {
// 'this' keyword maps to the global object
// here, 'this.firstName' is undefined.
return this.firstName + ' is friends with ' + friend;
}.bind(this));
console.log(arr);
}
Calling bind
will return a new copy of the map callback function but with a this
keyword mapped to the outer this
keyword, which is, in this case, will be the this
keyword referring to the object calling myFriends
.
3. We can create the callback of the map
function to be an arrow function:
Person.prototype.myFriends = function(friends) {
var arr = friends.map(friend => `${this.firstName} is friends with ${friend}`);
console.log(arr);
}
Now, the this
keyword inside the arrow function definition will share the lexical scope from its surroundings, which is the instance myFriends
that we have created.
All of the above three solutions should give us this result:
["John is friends with Emma", "John is friends with Tom"]
At this point, I hope that I have managed to make the this
keyword concept a little bit approachable for you. In this article, I shared some of the common situations that I have faced and how to deal with them, but of course, you will face more situations as you build more projects. I hope that my explanation can help you maintain a solid base when you approach the this
keyword binding topic. If you have any questions, suggestions or improvements I’m always happy to learn more and exchange knowledge with all of the awesome developers everywhere. Please feel free to write a comment, tweet me, or drop me a line!