Arrow functions and this keyword

Mar 17, 2023-
Vishwanath B.
Vishwanath
@frozeninretro

Arrow functions are a syntactical sugar to regular functions. You can shorten your syntax and make things look pretty. But should you use them everywhere? It's definitely tempting to. However, arrow functions have certain rules:

The first point is extremely important. Perhaps the most important rule of an arrow function is that it doesn't have its own this binding, like a regular function. In this post, we'll only cover the first point.

Let's take an example of setTimeout in an object method:

arrowFuncAndThis.js
const obj = {
  count: 10,
  doSomethingLater() {
    setTimeout(function () {
      // here, this refers to the Window object
      this.count++;
      console.log(this.count);
    }, 300);
  },
};

obj.doSomethingLater();
// logs "NaN", because the property "count" is not in the window/global scope.

The setTimeout() function creates a new execution context and the this keyword inside the function refers to the global object instead of the object obj. This is because in Javascript, the value of this is determined by how a function is called, not where it is defined.

You might have seen this code - let self = this OR let that = this in older codebases. Wonder why it's used?

By assigning this to the variable self/that before the setTimeout call, we capture a reference to obj, allowing us to access its count property later on.

arrowFuncAndThis.js
const obj = {
  count: 10,
  doSomethingLater() {
    // create a reference to "this",
    // and store it in a variable named self
    let self = this;
    setTimeout(function () {
      self.count++;
      console.log(self.count);
    }, 300);
  },
};

obj.doSomethingLater(); // logs "11"

Or, we could use bind to make sure the function executes in the proper scope.

arrowFuncAndThis.js
const obj = {
  count: 10,
  doSomethingLater() {
    setTimeout(
      function () {
        this.count++;
        console.log(this.count);
      }.bind(this),
      300
    );
  },
};

obj.doSomethingLater(); // logs "11"

Why does the setTimeout function create a new execution context?

Confused about what execution context is? I suggest you read this article.

  1. The setTimeout() function creates a new execution context because it uses a callback function that is executed after a specified delay.

  2. This execution context is different from the current execution context, which is the context in which setTimeout() was called.

In short;

Execution context of setTimeout is different from the execution context of callback func passed to setTimeout.

Instead, with arrow functions, we can simplify our code, and just modify our syntax to:

arrowFuncAndThis.js
const obj = {
  count: 10,
  doSomethingLater() {
    setTimeout(() => {
      this.count++;
      console.log(this.count);
    }, 300);
  },
};

obj.doSomethingLater(); // logs "11"

When an arrow function is used as the callback function passed to setTimeout(), it inherits the this value from the surrounding context lexically, which is the this value of the doSomethingLater() method of obj.

And, we know, this inside an object method refers to the object itself. So, arrow functions essentially "borrow" the this value from the surrounding context.

Arrow functions are a great feature of ES6, but use them properly.

If you want a detailed explanation on arrow functions and all the rules, you can check the official MDN documentation.