ES6 arrow functions and lexical this

Let’s take a look at one of the quirkiest behaviors in JavaScript. We’ll start by examining the legacy version of the language’s interpretation of the keyword this. Then we’ll consider some new behavior introduced by ES6’s arrow functions. How are they different? Which approach is more intuitive and/or useful? Read on for insight!

Prior to ECMAScript 2015 (a.k.a. ES6), JavaScript used an uncommon approach to the evaluation of this. It made object-orientation in a functional language difficult. For object-oriented design, this serves a critical purpose in creating objects that display polymorphism. The ECMAScript 5.1 specification interprets so-called ThisBinding in a manner often described as confusing and unintuitive.

ThisBinding is one of several execution contexts employed by the JavaScript interpreter to bind values to symbols. When passing functions as callbacks to other functions, the value of this at runtime might surprise you. For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Cupcake(type, frosting) {
this.type = type;
this.frosting = frosting;
this.announce = function() {
console.log('And now, the moment you\'ve been waiting for! Presenting...');
setTimeout(this._present, 1500);
};
this._present = function() {
console.log('...a ' + this.type +
' cupcake with ' + this.frosting + ' frosting');
};
}
var chocolateCupcake = new Cupcake('chocolate', 'buttercream');
chocolateCupcake.announce();

Everything looks like it should work. The announce() method is going to queue up some code for 1.5 seconds in the future. The function it passes to setTimeout refers to some information about the cupcake. These attributes are stored in that instance of the Cupcake object. The helper function _present() is defined in the same scope as the member variables this.type and this.frosting. So why do we get the following output?

1
2
And now, the moment you've been waiting for! Presenting...
...a undefined cupcake with undefined frosting

The problem is that when present() eventually goes off, the value of this is not this cupcake instance, even though it really looks like it should be. After all, we’re defining _present() in the same block of code where we set the variables this.type and this.frosting. In truth the value of this is decided later on, in the current execution context in which it was invoked. In the case of _present(), when it’s invoked 1.5 seconds later, we’re actually in the global scope. In that case, this refers to the global object window.

Options for ThisBinding

closure

So how can we make the keyword this resolve to the thing we actually want and not window? To write event-driven, asynchronous code we need our callbacks to know about the context in which they were defined, about the objects involved in the event. If this were just like any other variable, we wouldn’t have anything to worry about. In fact, assigning the value of this to a variable and then using that variable inside the callback provides one possible solution. The closure feature of the language, part of lexical scoping, makes this possible:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Cupcake(type, frosting) {
this.type = type;
this.frosting = frosting;
var self = this;
this.announce = function() {
console.log('And now, the moment you\'ve been waiting for! Presenting...');
setTimeout(this._present, 1500);
};
this._present = function() {
console.log('...a ' + self.type +
' cupcake with ' + self.frosting + ' frosting');
};
}
var chocolateCupcake = new Cupcake('chocolate', 'buttercream');
chocolateCupcake.announce();
1
2
And now, the moment you've been waiting for! Presenting...
...a chocolate cupcake with buttercream frosting

the bind method

Function.prototype.bind allows us to use a specialized version of the function that bakes in arguments and the this binding. When it’s invoked, the arguments are passed into the function as per usual. Also, the function is called as if it were invoked in the same context as the scope in which it was defined before being passed as a callback. Here’s an example of bind in action:

1
2
3
4
5
6
7
8
9
10
11
12
13
...
this.announce = function() {
console.log('And now, the moment you\'ve been waiting for! Presenting...');
setTimeout(this._present.bind(this), 1500);
};
this._present = function() {
console.log('...a ' + this.type +
' cupcake with ' + this.frosting + ' frosting');
};
}
...
1
2
And now, the moment you've been waiting for! Presenting...
...a chocolate cupcake with buttercream frosting

Enter ES6 arrow functions

Since we use anonymous functions so frequently in JavaScript, the new fat arrow functions in the ECMAScript 2015 spec are most welcome. They afford the following advantages:

  • concise syntax
  • lexical binding of this
  • several syntactic sugar features

Let’s take a look at the first two in action:

1
2
3
4
5
6
7
8
9
10
...
this.announce = function() {
console.log('And now, the moment you\'ve been waiting for! Presenting...');
setTimeout(() => {
console.log('...a ' + this.type +
' cupcake with ' + this.frosting + ' frosting');
}, 1500);
};
...

And now, the moment you've been waiting for! Presenting... ...a chocolate cupcake with buttercream frosting

Here’s another quick refactor using an arrow function and showing off its implicit return in a concise function body (no curly braces):

1
2
3
4
5
6
7
this.on('eat', (function() {
return this.type + ' cupcakes are delicious';
}).bind(this));
// this ^^ becomes this:
this.on('eat', () => (`${this.type} cupcakes are delicious`));

Arrow functions make it easy to inline anonymous functions in all those places we need them, such as asynchronous callbacks, higher order functions and event handlers. this works just like most people would expect. And the best part is, we can still .bind, .call , apply and otherwise use the old ES5 non-arrow functions to our heart’s content in ES6. The specification lets you mix and match both styles, so use whichever suits the situation.

caveats

Some libraries like jQuery and Backbone have their own strategies for this, which use ES5’s interpretation. For example, many jQuery callbacks use $(this) to pinpoint the target object out of the DOM.

1
2
3
$('#cupcake').click(function(e) {
$(this).append('<h1>more HTML</h1>');
});

An arrow function would cause the selector above to fail, since the context isn’t what jQuery expects. Backbone, on the other hand, passes around this as an additional parameter throughout its event system. Arrow functions should work fine with the framework and probably make Backbone code more concise:

1
2
3
4
5
6
7
8
9
this.collection.on('add', function() {
this.render();
}, this);
// from this ^^ to this:
myCollection.on('add', () => {
this.render();
});

Note that arrow functions can be invoked through .call and .apply but we can not pass in this as an argument, only regular variables. Also, arrow functions do not have their own arguments object. Rest parameters still work though. For other quirks and features of arrow functions, see the MDN reference.

Conclusion

I don’t think I’ve ever heard someone say something nice about legacy JavaScript’s specification for this binding. I personally find the behavior counterintuitive but workable. ES5’s notion of the keyword has its own logic and we must understand it. But going forward, the ES6 arrow function gives us another tool, one that delivers a little logical and syntactic sugar for our event-driven, async code.