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.
Copyright © 2018 Mithat Konar. All rights reserved.