naimulhaque.com

April 15th, 2023

A Closer Look at the 'this' keyword in JavaScript

An exploration of the values of JavaScript's 'this' keyword in different contexts, aiming to provide a deeper understanding of its behavior.

The this keyword is a unique identifier in JavaScript. The value of this is determined at runtime and can vary depending where you are using it. As a result, the behavior of the this keyword seems confusing to many people, especially to those who are new to the language or have limited experience with its nuances.

In other languages, e.g. C++, Java, C# etc, the this keyword represents the current instance of a class, But JavaScript's this keyword is a bit different in nature. So it's better not to compare with how it works in other languages.

This article aims to explore the value of the this keyword in different contexts. Let's dive in and unveil the mysteries of the this keyword!

this in global scope

When we use the this keyword in the global scope (i.e. outside any function), it's value refers to the global object.

Global object

The global object is the top-level object in the scope chain and acts as a container for all global variables (defined with the var keyword), functions, and objects.

In the browser environment, the global object is the window object. It provides access to various properties and methods related to the browser environment.

console.log(this); // the window object
console.log(window); // same as previous statement

Global functions like console.log, parseInt, setTimeout or global objects like document, Date etc. are properties of the window object.

console.log(console === window.console); // true
console.log(setTimeout === window.setTimeout); // true
console.log(document === window.document); // true

In Node.js environment, the global object is global object. It sounds strange as a sentence, but the identifier of the object itself is global. It's similar to how the window object is used in browsers but designed for server-side JavaScript execution.

console.log(this === global); // true
console.log(this === global.setTimeout); // true

this inside Functions

When a function is called, the value of the this keyword refers to the global object in non-strict mode, or undefined in strict mode.

// using the var keyword, so that the variable
// firstName gets attached with the global object
var firstName = "Naimul";
 
function sayHello() {
  console.log(`Hello, my name is ${this.firstName}`);
}
 
sayHello(); // Hello, my name is Naimul

The same piece of code when run in strict mode, It will throw an error “Cannot read property ‘name’ of undefined”. It’s because the value of this inside the function becomes undefined instead of referring to the global object.

Explicit function binding

Sometimes we need to explicitly bind the this keyword to a specific object to ensure that the function operates with the correct this value, regardless of how it is invoked.

We have three methods in JavaScript to accomplish this: call, apply, and bind, which we may call on any function. When a function is invoked with the call, apply, or bind, the this keyword will refer to what we pass in as the first parameter.

Function.prototype.call()

In the following example, we’re explicitly binding the this keyword to the person object. It takes the following input parameters:

  • thisArg: The value to be passed as the this context within the function.
  • arg1, arg2, ...: Optional arguments to be passed to the function individually.
const person = {
  firstName: "Naimul",
};
 
function sayHello(greet) {
  console.log(this === person); // true
  console.log(`${greet}, my name is ${this.firstName}`);
}
 
sayHello(); // Hi, my name is undefined
sayHello.call(person, "Hi"); // Hi, my name is Naimul

In this example, the sayHello function is called with the call method, which binds the this to the person object, so the output is "Hi, my name is Naimul".

Function.prototype.apply()

The apply method is another method that allows us to call a function and explicitly set the this value. However, unlike call, apply takes arguments as an array or an array-like object. It takes the following input parameters:

  • thisArg: The value to be passed as the this context within the function.
  • [arg1, arg2, ...]: An array containing the arguments to be passed to the function.
const person = {
  firstName: "Naimul",
};
 
function sayHello(greet) {
  console.log(`${greet}, my name is ${this.firstName}`);
}
 
sayHello.apply(person, ["Hi"]); // Hi, my name is Naimul

Function.prototype.bind()

Unlike call and apply, bind does not execute the function immediately but instead returns a new function where the this context is fixed to a specified object.

const person = {
  firstName: "Naimul"
};
 
function sayHello(greet) {
  console.log(`${greet}, my name is ${this.firstName}`);
}
 
const sayHelloWithPerson = sayHello.bind(person, "Hola");
sayHelloWithPerson();

Note that the greet parameter is pre-filled with "Hello". Even if we invoke it with a different greet value, sayHelloWithPerson("Hello") , it won’t use the new value.

this inside methods

When a method is called on an object in JavaScript, the this keyword inside that method refers to the object itself. This behavior is known as method binding and it's a fundamental aspect of how objects works in JavaScript. Let's look at the following example

const person = {
  firstName: "Naimul",
  greet: function () {
    // `this` refers to the object itself - the `person` object
    console.log(`Hello, my name is ${this.firstName}`);
  },
};
 
person.greet(); // "Hello, my name is Naimul"

What happens when we use arrow functions instead of regular functions? Let’s find out!

const person = {
  firstName: "Naimul",
  greet: () => {
    // `this` refers to the global object
    console.log(`Hello, my name is ${this.firstName}`);
  },
};
person.greet(); // "Hello, my name is undefined"

Arrow functions behave differently than regular functions. The arrow function printName captures the this value from the surrounding scope, which, in this case, is the global scope (the outermost scope).

It's generally recommended to use regular function expressions or function statements. It’s because you need to access the object’s properties or other methods.

Nested function

What happens if we have nested functions within a method? Let’s move the console.log in a nested function and call the function within the method. Trust me! We'll find out something very interesting.

const person = {
  firstName: "Naimul",
  greet: function () {
    function showGreetingMessage() {
      // `this` refers to the `global` object
      console.log(`Hello, my name is ${this.fullName}`);
    }
    showGreetingMessage();
  },
};
 
person.greet(); // "Hello, my name is undefined"

The value of this in the showGreetingMessage() function is the global object, because it is being invoked as a standalone function and not as a method of any object. It does not inherit the this value of the outer scope. To fix this issue, we can take several approach

  • Storing the this variable in a self variable, and use self inside the function
  • Calling the showGreetingMessage function with call, bind or apply
  • Switching to arrow functions

To fix this, we’ll convert our regular functions into arrow functions.

const anotherPerson = {
  fullName: "John Doe",
  greet: function () {
    const showGreetingMessage = () => {
      console.log(`Hello, my name is ${this.fullName}`);
    };
    showGreetingMessage();
  },
};
anotherPerson.greet();

By switching from regular functions to arrow functions, the this value is no longer redefined within the function, and instead, it inherits the this value from the surrounding myFriend object. This allows the arrow functions to access the correct this value, which is the myFriend object, and fix the issue with this not pointing to the right object.

The topic of arrow functions is broad and can require its own in-depth exploration. In a future article, we can dive deeper into advanced concepts related to arrow functions and best practices for their usage in different scenarios.

Constructor functions

When a function is called as a constructor with the new keyword, this is bound to the newly created object. For example:

// `this` refers to the newly created object
// which is returned by the new Person() call
function Person(name, age) {
  this.name = name;
  this.age = age;
}
 
const me = new Person("Naimul", 26);
 
console.log(me); // Person { name: 'Naimul', age: 26 }

this inside callback functions

Callback functions are functions that you pass to another function as an argument, so that they can be executed after some async operation (most of the time, but not always). Let’s try to assume what will be the output of the following function

function Person(name, age) {
  this.name = name;
  this.age = age;
  setTimeout(function () {
    console.log(this); // Window {...}
  }, 1000);
}
const me = new Person("Naimul", 26);

To fix this you can either explicitly bind the function to the proper this value or you can use arrow functions.

function Person(name, age) {
  this.name = name;
  this.age = age;
  setTimeout(() => {
    console.log(this); // Person { name: 'Naimul', age: 26 }
  }, 1000);
}
const me = new Person("Naimul", 26);

In conclusion, I hope this article has shed some light on the this keyword and now you understand it clearly in various contexts.