Comprehensively understanding the “this” keyword in JavaScript12 min read

The this keyword might be one of the most confusing and elusive parts in JavaScript because of its behavior in different contexts compared with other languages. In this article, we are going to learn how to use the this keyword properly and comprehensively understand what is this.

Before we start

In order to understand the this keyword in JavaScript, there are a few points we first need to consider. Firstly, the this keyword in JavaScript is a keyword, not a value, the this keyword’s value has nothing to do with the function itself, how the function is called determine the this value. Secondly, JavaScript is a scripting language, which means when you write some JavaScript code, the interpreter (not the compiler) reads the code and execute it line by line and the environment contains the line that is being executed is known as the execution context. And the this keyword might behave differently based on execution context – which is lied on the top of the stack where those execution contexts are being maintained.

The default “this” context

Let’s examine the default this value by opening up your web browser, then right-click choose the Inspect Element then go to the tab Console, or type Command + Shift + J (on macOS) or Ctrl + Shift + J (on Windows) to open the console, then when you get the console ready, type the following code:

console.log(this);

Then, hit enter, what you see? Yeah, you see the Window object. By default in JavaScript, the this value will refer to the root object or the Window object. The same rule is applied when we doing a simple function call:

function myFunction() {
    console.log(this); // [object Window]
}
myFunction();

When we run this function, it also gives us the same result that we did in the first piece of code, the this keyword refers to the global object which is the Window object. But when we enter the strict mode, the this value might be different, consider the code below:

function anotherFunction() {
    'use strict'
    return this;
}
anotherFunction() === Window // false

Comparing this function with the Window object, it doesn’t equal anymore. Because when we entering the strict mode and it realizes we just make a direct call to the anotherFunction() which isn’t a method or a property of an object (e.g Window.anotherFunction()) hence this comparison will return false. But instead, if we compare our function call with undefined, the result would be true:

anotherFunction() === 'undefined' // true

In general, in a global context or in simple function call without strict mode, the this value will always refer to the Window object ([object Window]).

“this” within the Objects

Object Literal

There are a few ways to create an object in JavaScript. With Object Literal, we simply create an object with a pair of curly brackets. Inside the Object Literal, the this value will always refer to its own object, not the Window object:

const obj = {};
obj.someMethod = function() {
    console.log(this); // obj
}
obj.someMethod();

In the code fragment above, we first create an object literal named obj, then we create a method (functions that are stored in object properties are called “methods”) named someMethod and inside this method, we console the this value, and finally we call someMethod then we get the result is the object itself because the Window object didn’t invoke the function, but our obj object did.

You might wonder, why it returns {someMethod: ƒ}, truthfully because it’s our obj object, it has one method which is someMethod, if you type obj in the console and enter, the result would be the same.

Object Constructor / Function Constructor

The same rule is also applied to the object constructor, the this value will refer to the object itself where the function is called:

function MyConstructorObject() {
    this.someMethod = function() {
        console.log(this); // MyConstructorObject 
    }
}
const obj2 = new MyConstructorObject();
obj2.someMethod(); // MyConstructorObject 

Why it returns MyConstructorObject and not obj2 simply because the object itself here is the MyConstructorObject, the obj2 just holds the reference to this object.

New Object Instances

With Function Constructor, as we just saw in the example above, we can create many objects based on this function constructor, and this function constructor works as a blueprint for creating new objects. When we invoke this function with the new keyword, we actually create a new instance from this function, and the this value will always refer to the newly created instance of this object. For example:

function Worker(workerName, workerAge, workerSalary) {
    this.name = workerName;
    this.age = workerAge;
    this.salary = workerSalary;
    this.getInfo = function() {
        console.log(`Name: ${this.name}, Age: ${this.age}, Salary: ${this.salary}`);
        console.log(this);
    }
}
let a = new Worker("Nam", 20, 100);
a.getInfo(); 
let b = new Worker("Andy", 24, 200);
b.getInfo();

Output:

Name: Nam, Age: 20, Salary: 100
Worker { name: 'Nam', age: 20, salary: 100, getInfo: [Function] }
Name: Andy, Age: 24, Salary: 200
Worker { name: 'Andy', age: 24, salary: 200, getInfo: [Function] }

So as you can see, the this value inside the getInfo() function behaves differently. In the first case, when we call a.getInfo(), the this value refers to the instance assigned to the variable a. And b.getInfo(), the this value refers to the instance stored in the variable b. We also have have another console.log(this) to point out the this value will refer to the object, in this case, the instance which calls the getInfo() function.

“this” with Events

This is the most interesting part when working with the this keyword, because when we handle events with JavaScript, the this keyword can be dynamic and the value could be changed greatly on the same function.

Let us create a simple HTML file to demonstrate the dynamic of this keyword:

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dynamic This</title>
</head>
<body>
    <div class="elem">
        <p>This keyword in JavaScript</p>
    </div>
    <button class="btn">Click me!</button>
    <script>
        const a = document.querySelector('.elem');
        const b = document.querySelector('.btn');
        function myFunction() {
            console.log(this);
        }
        a.addEventListener('click', myFunction); // <div class="elem">...</div>
        b.addEventListener('click', myFunction); // <button class="btn">Click me!</button>
        myFunction(); // [object Window]
    </script>
</body>
</html>

We’ve created a div that contains a paragraph and a simple button, then in between the script tags we write some JavaScript code. We first select element belonged with class elem and store it to the variable a, here this element is our div element, then we do the same thing to store the button element to our b variable. On top of that, we create a simple function myFunction then we use addEventListener for both variable a and b, pass into them the click events and the same myFunction function.

When we click to the div element, the console will print out this element, because this value will refer to the owner of this event, and in this case, it’s the div element, the same rule is applied to the button element. The last call is just identical as what we did before, we call the myFunction function, which hasn’t been attached to any object, hence, by default, we will see [object Window] in the console.

“this” in Arrow Function

With the introduction of the arrow function as one of the ES6’s features. There is a new way to write functions in JavaScript concisely. But before we dive in the this keyword in the arrow function, let’s examine one example:

const person = {
    name: "Nam",
    langs: ['JavaScript', 'Java', 'Python', 'Haskell'],
    tasks: function() {
        this.langs.forEach(function(lang) {
            console.log(`${this.name} wants to learn ${lang}`);
        });
    }
}
person.tasks(); 
/* 
undefined wants to learn JavaScript
undefined wants to learn Java
undefined wants to learn Python
undefined wants to learn Haskell
*/

What happened with Nam? Why this.name is undefined? Nam might be a cursed name then it cannot be printed out. You might expect that because the this keyword is inside a method in an object, definitely it should refer to the object contains this method, which is person. But actually do you notice the forEach method? This method accepts a callback function, in our case, it’s an anonymous function, then our this value is inside this function. And theoretically the this value inside this function also wrapped by the method tasks should be treated as belonging to this method, right? In JavaScript, no.

This is another remarkable point that we need to comprehend. The same old story when the this keyword is inside a function alone, this value will refer to the Window object. If the this value lies inside a function and this function is inside a method, the same rule also is applied as we can observe.

How to solve that? How can we bring the name to the console? There are several answers. One we can use the arrow function:

const person = {
    name: "Nam",
    langs: ['JavaScript', 'Java', 'Python', 'Haskell'],
    tasks: function() {
        this.langs.forEach(lang => {
            console.log(`${this.name} wants to learn ${lang}`);
        });
    }
}
person.tasks();

Output:

Nam wants to learn JavaScript
Nam wants to learn Java
Nam wants to learn Python
Nam wants to learn Haskell

The problem simply can be solved with the arrow function. In JavaScript ES6, the arrow function uses lexical scoping, which means it doesn’t bind it owns this, instead, it inherits one from the parent scope, which is our method tasks. this always refer to the same object of its parent which is outside the function.

However, if we use the arrow function as a method in an object, it has no binding scope and it inherits from the parent one which is the Window object, which leads to an unexpected result:

const obj = {
    someMethod: () => {
        console.log(this);
    }
}
obj.someMethod(); // [object Window]

Changing the “this” context with bind(), call() and apply()

With some of the examples above, there are a few problems arise that we want to change the context of a function. Functions in JavaScript are first-class objects which means they have their own methods. Thankfully JavaScript already has several methods that can help us accomplish our goal which are call(), apply() and bind() methods.

Call() and Apply() methods

The call() and apply() methods are almost identical with respect to their usage. The fundamental difference between call() and apply() is the call() method accepts an argument list, while apply() accepts a single array of arguments. The syntax might be:

/** Call method */
myFunction.call(anotherScope, arg1, arg2); // argument list
/** Apply method */
myFunction.aplly(anotherScope, [arg1, arg2]); // array of arugments

If we create a simple function and invoke this function by the call() method and don’t pass to this method any argument, it simply refers to the Window object by default:

function myFunc() {
    console.log(this);
}
myFunc.call(); // [object Window]

Now, for example, we have a function that references this but has no this context and we want to use this function to print out an object’s property:

function myFunction() {
    console.log(`My name is ${this.name}`);
}
const person = {
    name: "Nam",
    age: 19
}
myFunction() // My name is undefined

Actually, we cannot do person.myFunction() because this function isn’t a method of the object, so how we can get the name of this object from the function? We can use the call() method, like this:

myFunction.call(person); // My name is Nam
myFunction.apply(person); // My name is Nam

The first argument you pass to call will be what the this keyword inside that function is referencing, in our case, the this keyword will refer to our object, person.

In case we have an array with a number of items and we want to use this array with our object, it works perfectly with the call() method, for example myFunction.call(anotherScope, arr[0], arr[1], arr[2], arr[3] but as you can see it’s quite cumbersome. Like aforementioned, the apply() method accepts an array as the second argument, so it comes in handy when we just need to pass a single array instead of writing a single item in this array:

function myFunction(a, b) {
    console.log(`My name is ${this.name} and I know ${a}, ${b}.`);
}
const person = {
    name: "Nam",
    age: 19,
}
const langs = ['JavaScript', 'Java'];
myFunction.apply(person, langs); // My name is Nam and I know JavaScript, Java.

Bind() method

Sometimes, you might need to use a method over and over with the this context of another object. This might be ideal to use the bind() method, this method creates a new function that, when called, has its this keyword set to the provided value and it retains the original this context from the first time it was bound. It perhaps a little bit dubious for now, let’s take an example to clarify this:

const person = {
    name: "Nam",
    age: 69
}
function getInfo() {
    console.log(`My name is ${this.name} and I'm ${this.age} years old!`);
}
const a = getInfo.bind(person);
a(); // My name is Nam and I'm 69 years old!
const anotherPerson = {
    name: "Daniel",
    age: 24
}
a.bind(anotherPerson);
a(); // My name is Nam and I'm 69 years old!

As we can see in the example, we first bind the function to the object person, it works fine as call() or apply() methods that give us the value inside the object, again we want to bind the function to another object second times nonetheless it retains the original this context from the first time it was bound. So we call the function twice and get the same result.

Summary

In order to understand this keyword in JavaScript takes a little time and patient. To sum up, this in JavaScript is a keyword, not a value, the function itself doesn’t determine the value of this but instead how function is called does. In the global context or simple function call without strict mode, the this value will always refer to the Window object. Inside an object, the this value refer to the owner object, the same rule applies when creating new object instance, or with function constructor. The value of this can be dynamic when working with events. With arrow functions, this has no binding and it inherits from the parent scope, which called lexical scope. To change the context of a function, we can either use call(), apply() or bind() methods.

0 0 votes
Article Rating
Previous Article
Next Article
Subscribe
Notify of
guest
0 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments
Every support is much appreciated ❤️

Buy Me a Coffee

0
Would love your thoughts, please comment.x
()
x