The ‘this’ keyword in JavaScript, demystified

Mohammed Abdullatif
ITNEXT
Published in
10 min readFeb 11, 2019

--

Photo by Simon Abrams on Unsplash

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, thisrefers 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:

  1. By Assigning this inside myFriends function body to another variable, called self for example and using it inside the map 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!

--

--