JavaScript and ‘this’, arrow function edition

In a previous installment, we took a dive into the this variable and how it behaves in different ES5 situations. In this installment, we’ll do the same but for so-called arrow functions, introduced in ES6.

In ES5 functions this is bound at call time

We saw in our previous discussion that with ES5 function syntax, “this can be thought of as an additional parameter to the function that is bound at the call site. If the function is not called as a method then the global object (non-strict mode) or undefined (strict mode) is passed as this.”[1]

Thus, in the following code:

var fuzz = {
  bar: function () {
    setTimeout(function() {
      console.log(this);  
    }, 1000)
  }
};

fuzz.bar();  // Window about:home

fuzz.bar() outputs the window because the anonymous callback function in setTimeout isn’t attached to the object. The standard ES5 solution is to fake a lexically scoped this:

var fuzz = {
  bar: function () {
    var _this = this;  // _this is lexically scoped
    setTimeout(function() {
      console.log(_this);  
    }, 1000)
  },
};

fuzz.bar();  // Object { bar: bar() }

or explicitly bind the function to the desired this:

var fuzz = {
  bar: function () {
    setTimeout(function () {
      console.log(this);  
    }.bind(this), 1000)
  },
};

fuzz.bar();  // Object { bar: bar() }

The this at the point the bind call is made scopes to the object, and so that gets bound to the context in which the anonymous function runs.

In arrow functions this is lexically scoped

The important thing to know regarding this and arrow functions is that inside arrow functions this is always lexically scoped.

Here is the same object literal but with the anonymous callback replaced with an arrow function:

let fuzz = {
  bar: function () {
    setTimeout( () => {
      console.log(this);  
    }, 1000)
  }
};

fuzz.bar();  // Object { bar: bar(), baz: baz() }

Much less code, much less ambiguity. All good.

You can use arrow functions for methods defined in constructors as well, and they work the same way.

function Thing() {
  this.bar = function() {
    setTimeout(function() {
      console.log(this);  
    }, 1000)
  };
  
  this.baz = function() {
    setTimeout(() => {console.log(this)}, 1000)
  };

  this.qux = () => { setTimeout( () => {console.log(this)}, 1000 ) };
};

let a = new Thing();

a.bar();  // Window about:home
a.baz();  // Object { bar: bar(), baz: baz() }
a.qux();  // Object { bar: bar(), baz: baz() }

bar uses conventional ES5 syntax for everything, baz uses arrow syntax only for the callback, and qux uses arrow syntax for the function definition as well as the callback. this will be bound to the object in all three, but only the last two bind this to the object inside nested function definitions.

Caveat

So you’re thinking, “Great. I’ll just forget about ES5 syntax when writing methods and use arrow syntax exclusively.” That would be too easy. If you use arrow syntax to define a method in an object literal this will not be bound to the object!

let b = {  
  baz: () => {
    console.log(this);
  }
};

b.baz();  // Window about:home (!!!)

It’s probably not the expected behavior. However understanding it will require a deeper dive into the plumbing of JS that I think is useful here. Just remember that this in object literals’ method body definitions using arrow syntax won’t be the object — otherwise it will. It’s also good to know that as of this writing a popular JS framework admonishes you against using arrow functions in certain contexts so as to keep things unbroken.

Yeah, this is still a bit of a quirky mess.

[1] Adapted from chuckj’s answer to a StackOverflow question. I have modified it to account for differences between ES5’s strict and non-strict modes.

Leave a Reply

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