Learnitweb

‘this’ keyword in JavaScript

1. Introduction

The this keyword is one of the most perplexing concepts in JavaScript. The confusion arises from the fact that its value is determined differently depending on the context in which it is used. The this keyword can be used in different contexts: inside functions, global scope, inside modules, etc.

2. Function context

The this keyword is mostly used inside functions to refer to the object using which the function was invoked. When a function is called as a “method” using an object, the this keyword refers to the object that invoked the function. The value of this inside a function depends on how that function is called. Consider the following code example:

const student = {
  id: 123,
  name: "John",
  email: "john@email.com",
  printInfo: function () {
    console.log(`${this.id} - ${this.name} - ${this.email}`);
  },
};

student.printInfo();
// 123 - John - john@email.com

The this inside that function refers to the object using which the function was invoked. So in our code example, this inside the printInfo refers to the student object. As the student object has the three properties that are accessed using the this keyword inside the printInfo function, their values are logged to the console.

function printName() {
  console.log("The name is: " + this.fullName);
}

printName();
//The name is: undefined

What does this refer to inside the printName function?

The answer to the question above depends on whether our code is executed in strict mode. If non-strict mode, this inside a function, when not invoked as a method, refers to the global object, which in the case of browsers is the window object. However, the value of this inside a function is undefined in strict mode when not invoked as a method.

3. Global context

In the global scope, the value of this varies depending on the environment where the JavaScript code is executed. JavaScript code runs in various environments, such as browsers or NodeJS. The value of this in the global scope differs depending on the environment. In browsers, this refers to the window object when in the global scope.
In NodeJS, the value of this varies depending on whether you’re using ECMAScript modules or CommonJS modules. In ECMAScript modules, this is undefined at the top level due to the code being executed in strict mode. In CommonJS modules, at the top level, this refers to the module.exports object.

Inside web workers, the value of this at the top level refers to the global scope of the web worker, which is different from the global scope containing the window object in the browser. Code inside a web worker is executed in its own separate context with its own global scope.

4. Constructor function context

When a function is invoked as a constructor function using the new keyword, the this keyword inside the constructor function refers to the newly created object. The new keyword creates a new object and sets the newly created object as the value of this. As a result, we can use this inside a constructor function to add properties to the newly created object.

function Recipe(name, ingredients) {
  this.name = name;
  this.ingredients = ingredients;
}

The function above, when invoked as a constructor function, will add two properties: name and ingredients to the newly created object.

5. Class context

Code inside a class in JavaScript is executed in strict mode. As a result, the value of this inside methods is either undefined if not invoked on an object or the class instance itself, which is used to invoke the method.

class Shape {
  constructor(color) {
    this.color = color;
  }

  printColor() {
    console.log(this.color);
  }
}

const circle = new Shape("Red");
const printColorFn = circle.printColor;
printColorFn();
// Error: this is undefined

The code example above throws an error because we have invoked the printColor method as a “function”. As mentioned earlier, code inside a class executes in strict mode, so, as within functions in strict mode, this inside methods is undefined.

6. DOM event handler context

What happens with callback functions that we don’t explicitly invoke? For example, consider DOM event handlers—callbacks that JavaScript calls on our behalf when a click event occurs. In such scenarios, what does the value of this represent?

The event listener callback is executed with this bound to the HTML element that triggered the event. For instance, consider the following example:

<button>Submit</button>

 <script>
 const btn = document.querySelector("button");

 class FormHandler {
    constructor(submitBtn) {
      submitBtn.addEventListener("click", this.submitForm);
    }

    submitForm() {
      console.log("form submitted");
      console.log(this);
    }
 }

 new FormHandler(btn);
 </script>

When this method is called as an event listener callback, the value of this refers to the button element that triggered the event, rather than the instance of the class as one might typically anticipate.

7. Arrow functions

Another way to solve the problem shown above is to use an arrow function. Let’s change the code example above to use an arrow function:

Unlike regular functions, which get their own value of this when they are invoked, arrow functions don’t get their own this value; instead, the value of this inside an arrow function is taken from the surrounding context.

JavaScript also offers mechanisms to explicitly define the value of this as per our requirements. We can use any of the following three built-in methods to explicitly set the value of this:

  • Function.prototype.call()
  • Function.prototype.apply()
  • Function.prototype.bind()

8. Borrowing methods

Consider an object with methods that could be beneficial for other objects too. How can we make those methods available to other objects? One approach is to replicate the method definitions for each object that needs them. However, duplicating code isn’t ideal. Is there a way to prevent duplication while reusing the same methods effectively?

const john = {
  name: "John",
  sayHello() {
    console.log("Hello, I am " + this.name);
  },
};

const sarah = {
  name: "Sarah",
};

// borrow method from john
const sayHello = john.sayHello;
sayHello.call(sarah);
// Hello, I am Sarah

We can explicitly set the value of this within a function and apply it to other objects using any of the three methods discussed earlier. This approach helps us avoid code duplication and promotes reuse.

You might wonder: why not create a constructor to generate objects and define shared methods on the constructor’s prototype property? That’s a valid point—using a constructor is the ideal approach when creating similar objects. However, explicitly setting the value of this provides a flexible way to share code between unrelated objects. It’s a useful option that can be applied in the right context.

9. Chain constructor calls

Prior to the introduction of classes in JavaScript, the conventional method for inheriting from another constructor function involved manually setting the prototype chain and reusing the inherited constructor to assign common properties to the new object. The example below demonstrates how an existing constructor can be leveraged to initialize properties in a newly created object:

function Employee(name, age, id) {
  this.name = name;
  this.age = age;
  this.id = id;
}

function BankEmployee(name, age, id, bankName) {
  // delegate the responsibility of adding
  // "name", "age", and "id" properties to
  // the Person constructor
  Employee.call(this, name, age, id);
  this.bankName = bankName;
}

In simpler terms, the Employee constructor is called within the BankEmployee constructor, with this explicitly bound to the newly created BankEmployee instance. This ensures that any properties assigned to this within the Employee constructor are actually applied to the BankEmployee object. This approach allows us to utilize the existing constructor function, minimizing code duplication.

10. globalThis

globalThis is a standard global object in JavaScript that provides a consistent way to access the global context, regardless of the environment (browser, Node.js, or others). In different environments, the global object has various names:

  • In browsers, it’s typically window or self.
  • In Node.js, it’s global.

With globalThis, you can access the global object without worrying about the environment. For example:

console.log(globalThis); // Outputs the global object in any environment

It was introduced in ECMAScript 2020. Provides a unified way to access the global scope, eliminating the need to check for window, global, or other environment-specific global objects. Useful in cross-platform or universal JavaScript code.

if (typeof window !== "undefined" && window.secretProperty) {
  // execute code for browser
} else if (typeof global !== "undefined" && global.secretProperty) {
  // execute code for nodejs
}

With globalThis, we can simplify the above code as shown below:

if (globalThis.secretProperty) {
  // execute code
}