Arrow Function Semantics
In the last post, we took a deep-dive into the syntax of JavaScript’s new arrow functions. It’s great to see a new language feature that’s able to remove so much boilerplate from an idiomatic ES5 example.
But arrow functions are much more than function expressions with a fresh coat of paint. On top of the new appearance, there are some significant differences in the way they behave too.
This post explores these new behaviours; including why arrow functions don’t have their own values for this
or arguments
, and what that means for the code you write.
No more this
Before arrow functions, every function call would create a special variable and add it to the scope - this
. What this
equalled depended on how you called the function. Call it using .call(o)
, .apply(o)
or as a method on o
, and this
would be equal to o
.
This behaviour becomes an issue when you start using nested functions, causing one of the most famous JavaScript gotchas.
Instead of adding and removing the ‘hidden’ class when the #extra-info
element is clicked, this code will result in errors being printed to the console.
In the click-handler function we pass to el.addEventListener
, this
doesn’t refer to our CollapseableSection
instance. Instead, the click-handler creates it’s own value for this
, which gets set to undefined
. The inner function’s this
shadows the this
we really want, in the same way that variables defined in inner functions shadow variables of the same name defined in the outer scope.
Lexical this
Because arrow functions don’t create their own this
, they get to re-use the this
available in the outer scope. That leads to a neat fix to our above example:
Wrong methodology
As with most design choices, arrow functions this
-lessness comes with a downside. Since they don’t have their own this
, you can’t use them to define methods that need access to the object on which they’re called.
No more arguments
In regular functions, arguments
is another special variable created at call time. Its main job is to provide a list of the arguments the function was called with. This is a hugely popular feature - used to create variadic functions, functions that accept an unknown number of arguments.
For instance, as an alternative to writing a sum
function that operates on an array of numbers (i.e. sum([1, 2, 3, 4])
), you could write the following:
If you’ve not run into this before, you might be asking yourself “but what’s the [].slice.call(arguments)
all about.
The issue is that arguments
has never been an array. It’s been an array-like; an object with a .length
property and numeric properties. That might not matter if you intend to use a for(;;;)
loop, but if you want to leverage the modern array methods, you’ll have to first convert to an array. That’s what the [].slice.call(arguments)
trick is for.
In arrow functions, there is no arguments
created - just like there’s no this
created - when the function is called. Instead, you can use the new spread syntax to express that you want to take a variable number of arguments, and turn them into a real array.
Much cleaner.
Inferring a .name
When JavaScript engines print stack traces, it’s common for them to look at the .name
property of a function to find out what they should print out for the user to see.
But we’ve seen that arrow functions are look anonymous - they don’t seem to have a name, the same way that a plain function expression doesn’t have a name when written in this form.
Of course, in conversation and in code, we think about the function as having the name fn
- the name of the variable to which it’s bound. In ES2015, whenever you create an arrow function and immediately assign it to a variable or property, the .name
variable gets set.
Wrapping up
I haven’t actually covered all of the new behaviours of arrow functions here. Since ES2015 is a pretty hefty advance in JavaScript, there are lots of new language features that arrow functions interact with too - the super
keyword, tail-call optimisation, generators, and more. But hopefully this will be enough of an overview for you to see the benefits - and drawbacks - of using the new form.