JavaScript's keyword this is a source of pain for a lot of people. A common question goes something like this:

"I make call a Firebase function, and inside that callback, I need to access the this to update my React component."

Another common case is setTimeout:

"The problem is setTimeout takes a callback, but I need to access this and setTimeout changes it!"

A basic guideline

In normal circumstances, this refers to whatever is to the left of the dot at calltime. There are a few exceptions, which specifically alter the behavior of this—call, apply and bind. If you haven't used one of those functions to alter the behavior of this, then look to the left of the dot at calltime.

An example

var alarm = {
  ringer: function () {
    console.log("The " + this.color + " alarm: Ring!!!");
  },
  color: "red"
}

alarm.ringer();

This is pretty straight-forward. When you call alarm.ringer();, the thing to the left of the dot is alarm. So inside the ringer function, this is the alarm and the color is red.

An error in a setTimeout callback

var alarm = {
  ringer: function () {
    console.log("The " + this.color + " alarm: Ring!!!");
  },
  set: function (milliseconds) {
    setTimeout(function () { this.ringer() }, milliseconds);
  },
  color: "red"
}

alarm.set(2000);

The goal here was that the alarm would ring two seconds later. Unfortunately, we get this error:

The reason is that setTimeout calls the callback passed into it. Based on the error, we can see that the setTimeout function created a Timeout object and stored the callback function we passed into it as Timeout._onTimeout. And at the time it was called, the thing to the left of the dot is the timeout, not the alarm.

The classic solution

The way the "this" problem has traditionally been solved is by assigning the "this" from the outer scope to a new variable called either "_this" or "that".

var alarm = {
  ringer: function () {
    console.log("The " + this.color + " alarm: Ring!!!");
  },
  set: function (milliseconds) {
    var that = this;
    setTimeout(function () { that.ringer() }, milliseconds);
  },
  color: "red"
}

alarm.set(2000);

Now it works. Since the value of "this" inside the set function is the alarm, and that is just a regular variable. Since functions can access variables from their surrounding scope, that.ringer() inside the callback is alarm.ringer().

The red alarm: Ring!!!

The ES6 arrow function solution

ES6 arrow functions don't work quite the same way as normal function expressions. They don't have their own bindings to "this", so any references to "this" use the bindings from the enclosing function.

let alarm = {
  ringer: function () {
    console.log("The " + this.color + " alarm: Ring!!!");
  },
  set: function (milliseconds) {
    setTimeout(() => { this.ringer() }, milliseconds);
  },
  color: "red"
}

alarm.set(2000);

As the MDN link above mentions, arrow functions are ill-suited for methods, but they are a convenient way to simplify callbacks. In 2019, most JS devs use arrow functions to solve this problem and only encounter the "this = that" pattern in libraries and legacy code.

Leave a Reply

Your email address will not be published. Required fields are marked *