The this keyword in JavaScript has different values based on its context. Ignoring this important detail can lead to confusion and bugs in your code. In this article, we will explore the different behaviors of this and how to use it effectively.

The this Keyword in Strict Mode

In strict mode, the this keyword outside of any object is always undefined. However, in the default sloppy mode, this refers to the global object (window in a browser context), unless specific cases override this behavior.

The this Keyword in Methods

A method in JavaScript is a function that is attached to an object. When using a regular function as a method, this is automatically bound to the object it belongs to. Take a look at the example below:

const car = {
  maker: 'Ford',
  model: 'Fiesta',

  drive() {
    console.log(`Driving a ${this.maker} ${this.model} car!`);
  }
}

car.drive();
// Output: Driving a Ford Fiesta car!

You can also achieve the same result by declaring the method using the function keyword:

const car = {
  maker: 'Ford',
  model: 'Fiesta',

  drive: function() {
    console.log(`Driving a ${this.maker} ${this.model} car!`);
  }
}

However, arrow functions behave differently. They are lexically bound, meaning that this is derived from the context where they are defined. As a result, arrow functions do not have their own this binding. Here’s an example:

const car = {
  maker: 'Ford',
  model: 'Fiesta',

  drive: () => {
    console.log(`Driving a ${this.maker} ${this.model} car!`);
  }
}

car.drive();
// Output: Driving a undefined undefined car!

Binding Arrow Functions

Unlike regular functions, arrow functions cannot have their this value bound to a specific object. The binding is not possible due to the way they work.

Explicitly Binding the this Value

JavaScript provides several methods to bind the this value to a specific object. Here are a few examples:

  • Using the bind() method during function declaration:
const car = {
  maker: 'Ford',
  model: 'Fiesta'
}

const drive = function() {
  console.log(`Driving a ${this.maker} ${this.model} car!`);
}.bind(car);

drive();
// Output: Driving a Ford Fiesta car!
  • Binding an existing object method to a different object:
const car = {
  maker: 'Ford',
  model: 'Fiesta',

  drive() {
    console.log(`Driving a ${this.maker} ${this.model} car!`);
  }
}

const anotherCar = {
  maker: 'Audi',
  model: 'A4'
}

car.drive.bind(anotherCar)();
// Output: Driving a Audi A4 car!
  • Using the call() or apply() method during function invocation:
const car = {
  maker: 'Ford',
  model: 'Fiesta'
}

const drive = function(kmh) {
  console.log(`Driving a ${this.maker} ${this.model} car at ${kmh} km/h!`);
}

drive.call(car, 100);
// Output: Driving a Ford Fiesta car at 100 km/h!

drive.apply(car, [100]);
// Output: Driving a Ford Fiesta car at 100 km/h!

The first parameter passed to call() or apply() binds this to the specified object. The difference between call() and apply() lies in how the arguments are passed. call() accepts a variable number of parameters, while apply() requires an array of arguments.

The Special Case of Browser Event Handlers

In event handler callbacks, this refers to the HTML element that received the event. To bind this to a specific context, you can use the bind() method. Here’s an example:

document.querySelector('#button').addEventListener(
  'click',
  function(e) {
    console.log(this); // Refers to the HTML element
  }.bind(this)
);

Understanding the behavior of the this keyword in JavaScript is crucial for writing clean and bug-free code. It’s worth taking the time to familiarize yourself with the different use cases and tricks to leverage this effectively.