ponyfoo.com

ES6 Arrow Functions in Depth

Fix
A relevant ad will be displayed here soon. These ads help pay for my hosting.
Please consider disabling your ad blocker on Pony Foo. These ads help pay for my hosting.
You can support Pony Foo directly through Patreon or via PayPal.

The daily saga of es6-in-depth articles continues. Today we’ll be discussing Arrow Functions. In previous articles we’ve covered destructuring and template literals. I strive to cover all the things when it comes to the ES6 feature-set – and eventually we’ll move onto ES7. I find that writing about these features makes it way easier for them to become engraved in my skull as well.

Since you’re reading these articles, I suggest you set up Babel and babel-node, and follow along by copying the self-contained examples into a file. You can then run them using babel-node yourfile in the terminal. Running these examples on your own and maybe tweaking them a little bit will help you better internalize these new features – even if you’re just adding console.log statements to figure out what’s going on.

Now onto the topic of the day.

We’ve already kind of went over arrow functions in previous articles, using them in passing without a lot of explaining going on. This article will focus mainly in arrow functions and keep the rest of ES6 in the back burner. I think that’s the best way to write about ES6 – making a single feature “stand out” in each article, and gradually adding the others and interconnecting the different concepts so that we can understand how they interact together. I’ve observed a lot of synergy in ES6 features, which is awesome. It’s still important to make a gradual dive into ES6 syntax and features and not jump into the water as it’s warming up, because otherwise you’ll have a bad time adjusting to the new temperature – that was probably a bad analogy, moving on.

Using Arrow Functions in JavaScript

Arrow functions are available to many other modern languages and was one of the features I sorely missed a few years ago when I moved from C# to JavaScript. Fortunately, they’re now part of ES6 and thus available to us in JavaScript. The syntax is quite expressive. We already had anonymous functions, but sometimes it’s nice to have a terse alternative.

Here’s how the syntax looks like if we have a single argument and just want to return the results for an expression.

[1, 2, 3].map(num => num * 2)
// <- [2, 4, 6]

The ES5 equivalent would be as below.

[1, 2, 3].map(function (num) { return num * 2 })
// <- [2, 4, 6]

If we need to declare more arguments (or no arguments), we’ll have to use parenthesis.

[1, 2, 3, 4].map((num, index) => num * 2 + index)
// <- [2, 5, 8, 11]

You might want to have some other statements and not just an expression to return. In this case you’ll have to use bracket notation.

[1, 2, 3, 4].map(num => {
  var multiplier = 2 + num
  return num * multiplier
})
// <- [3, 8, 15, 24]

You could also add more arguments with the parenthesis syntax here.

[1, 2, 3, 4].map((num, index) => {
  var multiplier = 2 + index
  return num * multiplier
})
// <- [2, 6, 12, 20]

At that point, however, chances are you’d be better off using a named function declaration for a number of reasons.

  • (num, index) => is only marginally shorter than function (num, index)
  • The function form allows you to name the method, improving code quality
  • When a function has multiple arguments and multiple statements, I’d say it’s improbable that six extra characters will make a difference
  • However, naming the method might add just enough context into the equation that those six extra characters (plus method name) become really worthwhile

Moving on, if we need to return an object literal, we’ll have to wrap the expression in parenthesis. That way the object literal won’t be interpreted as a statement block (which would result in a silent error or worse, a syntax error because number: n isn’t a valid expression in the example below. The first example interprets number as a label and then figures out we have an n expression. Since we’re in a block and not returning anything, the mapped values will be undefined. In the second case, after the label and the n expression, , something: 'else' makes no sense to the compiler, and a SyntaxError is thrown.

[1, 2, 3].map(n => { number: n })
// [undefined, undefined, undefined]
[1, 2, 3].map(n => { number: n, something: 'else' })
// <- SyntaxError
[1, 2, 3].map(n => ({ number: n }))
// <- [{ number: 1 }, { number: 2 }, { number: 3 }]
[1, 2, 3].map(n => ({ number: n, something: 'else' }))
/* <- [
  { number: 1, something: 'else' },
  { number: 2, something: 'else' },
  { number: 3, something: 'else' }]
*/

A cool aspect of arrow functions in ES6 is that they’re bound to their lexical scope. That means that you can say goodbye to var self = this and similar hacks – such as using .bind(this) to preserve the context from within deeply nested methods.

function Timer () {
  this.seconds = 0
  setInterval(() => this.seconds++, 1000)
}
var timer = new Timer()
setTimeout(() => console.log(timer.seconds), 3100)
// <- 3

Keep in mind that the lexical this binding in ES6 arrow functions means that .call and .apply won’t be able to change the context. Usually however, that’s more of a feature than a bug.

Conclusions

Arrow functions are neat when it comes to defining anonymous functions that should probably be lexically bound anyways, and they can definitely make your code more terse in some situations.

There’s no reason why you should be turning all of your function declarations into arrow functions unless their arguments and expression body are descriptive enough. I’m a big proponent of named function declarations, because they improve readability of the codebase without the need for comments – which means I’ll have “a hard time” adopting arrow functions in most situations.

That being said, I think arrow functions are particularly useful in most functional programming situations such as when using .map, .filter, or .reduce on collections. Similarly, arrow functions will be really useful in asynchronous flows since those typically have a bunch of callbacks that just do argument balancing, a situation where arrow functions really shine.

Liked the article? Subscribe below to get an email when new articles come out! Also, follow @ponyfoo on Twitter and @ponyfoo on Facebook.
One-click unsubscribe, anytime. Learn more.

Comments (7)

Barney Carroll wrote

An interesting gotcha with lexical scope: as mentioned, arrow functions don’t have this, but they also don’t have an arguments reference. The argument against named function expressions is that more expressive code, with less grammar and punctuation, is actually easier to read and reason about — rather than getting bogged down with cruft describing the programmer’s mental model, you see what the code actually does immediately.

Jovica aleksic wrote

True indeed, however you do have rest args / spread, which can substitute the missing arguments object in most cases

Barney Carroll wrote

You’re right — and Nic’s previous article on destructuring provides a good insight into that. The frustration for me is that you can’t have a single expression that creates references to the larger structure and it’s components. For example, I may want to create a ‘guard’ function which tails off by passing all the arguments to another function if certain criteria are met. If the first argument given is an object, and this particular function’s internals want to destructure some of that object’s properties, then I can no longer refer to the whole object, or indeed the whole arguments object. But I suppose this is veering off into bad API design / destructuring / ES7 proxies :)

Martin Segado wrote

which would result in a silent error or worse, a syntax error

…haha, I think I’d actually prefer the syntax error myself! Easier to debug =)

Arturo wrote

What do you think of using the arrow syntax for nesting functions?

const something = foo => bar => baz => {
  // ..
  return baz(bar)
}

for

function something (foo) {
  return function (bar) {
    return function (baz) {
      // ..
      return baz(bar)
    }
  }
}

Is this “very clever” or “no, no”?

Chuck Conway wrote

I wouldn’t do either. It’s confusing. Use a class or pass in the function. I have an OO background, so I’d create a class.

Nicolas Bevacqua wrote

In general, when it comes to coding style, "very clever" === "no, no" in my mind :)