February 23, 2018 · Javascript ES6

ES6 arrow functions are weird... and awesome!

Last time, we talked about how arrow functions don't have their own this. Hopefully that gave you a taste of what makes arrow functions unique. However, that's not the end of the story though. Arrow functions are a different animal altogether. For this post, we're going to keep talking about how weird arrow functions are. In doing so, we'll hopefully gain more insight into when and how to use arrow functions in an idiomatic way when we write Javascript code.

A Different Animal

No arguments object

Other than having no this, arrow functions also have no arguments object. So if you refer to arguments in an arrow function, you're actually referring to the arguments object of the surrounding scope.

function justAFunction() { console.log(arguments); }
const myArrowFn = () => console.log(arguments);

justAFunction(); // [object Arguments] { ... }
myArrowFn(); // ReferenceError: arguments is not defined
No propotype property

There's actually more to the story though. Arrow functions have no prototype property either.

function justAFunction() {}
const myArrowFn = () => {};

console.log(justAFunction.prototype); // prints [object Object] { ... }
console.log(myArrowFn.prototype); // prints undefined
Can't be used as a constructor

As such, arrow functions can't be used as constructors. An error will be thrown if you try to invoke an arrow function with the new keyword.

const myArrowFn = () => {};
function justAFunction() {}

console.log(new justAFunction()); // prints [object Object] { ... }
console.log(new myArrowFn()); // prints TypeError: myArrowFn is not a constructor
Summary

So what does all this mean? Well, arrow functions simply weren't meant to replace normal functions in Javascript. They're a different entity altogether. So how do we decide when to use arrow functions and when to use good old plain functions? Let's start by talking about when we definitely shouldn't use arrow functions.

When not to use arrow functions

DON'T use arrow functions as constructors

As you just saw, arrow functions shouldn't be used as constructors. An error will be thrown if you try to do this.

const myArrowFn = () => {};
console.log(new myArrowFn()); // prints TypeError: myArrowFn is not a constructor
DON'T use arrow functions as methods

Since arrow functions don't have their own this, they shouldn't be used as methods either.

const greeter = {
  addressee: "feller", 
  greet: (time) => {
    console.log("Good " + time + ", " + this.addressee + "!");
  },
};

greeter.greet('morning'); // "Good morning, undefined!"

In the code above, greet is an arrow function, so it has no execution context of its own. So this refers to the execution context of the surrounding scope. If you think about it though, there's actually no surrounding function being executed at this point. So by default, this refers to the global object, or window if we're in the browser. Suffice it to say that this is rarely what we want when we're creating an object and defining its methods.

When to use arrow functions

Given what we've covered so far, we can conclude a few things about when we should use arrow functions.

DO use arrow functions to achieve non-binding of this

We should use arrow functions when we need to retain access to the outer scope's this. In such cases, we should use an arrow function to define the innermost function. The arrow function's non-binding of this will ensure that inside it, we retain access to the surrounding this.

const greeter = {
  times: ["morning", "afternoon", "night"],
  addressee: "feller", 
  greet: function() {
    this.times.forEach(time => {
      console.log("Good " + time + ", " + this.addressee + "!");
    });
  }
};

greeter.greet(); 
// "Good morning, feller!"
// "Good afternoon, feller!"
// "Good night, feller!"
DO use arrow functions to write short standalone functions

Sometimes we don't need to retain access to this in a callback function. In such cases, we may still opt to use arrow functions simply because of their shorter and more concise syntax.

const numbers = [1,2,3,4,5,6,7,8,9,10];
const isOdd = num => num % 2 == 1;
const odd = numbers.filter(isOdd);
console.log(odd); // [1, 3, 5, 7, 9]

const nums = [1,2,3,4,5];
const doubled = nums.map(num => num * 2);
console.log(doubled);

This is what it would look like if we wrote the same code without arrow functions.

const numbers = [1,2,3,4,5,6,7,8,9,10];
function isOdd(num) { return num % 2 == 1; }
const odd = numbers.filter(isOdd);
console.log(odd); // [1, 3, 5, 7, 9]

const nums = [1,2,3,4,5];
const doubled = nums.map(function(num) {
  return num * 2;
});
console.log(doubled);

The arrow function syntax is definitely cleaner and more readable.

Arrow Function Syntax

Before we end, let's quickly go over some syntactical concerns. Since this blog is supposed to be focused on first principles, I usually don't like to write about syntax issues. However, syntax was something that really tripped me up when I first started using arrow functions. That's because there are a lot of different ways to write arrow functions.

Variation 1: Multiline function body

Let's start with the function body. When the arrow function is a multiliner, the function body needs to be enclosed within curly braces {} and an explicit return statement is required.

const myArrowFn = () => {
  console.log("did stuff");
  return "something after doing stuff";
};
Variation 2: Single line function body

When the arrow function is a one liner, it can be written in the same style as above. Alternatively, the function body may be enclosed in parentheses (), with no explicit return statement or semicolon ; after the expression. Keep in mind that if you do choose the implicit style, the Javascript parser will complain if it sees an explicit return or semicolon ;.

const explicitOneLiner = () => { 
  return "something";
};

const implicitOneLiner = () => (
  "something else"
);
Variation 3: Inline function body

If the arrow function is short enough, you may choose to write it inline style. An inline function body is really just a special subset of a single line function body. So you can choose to write it in either an explicit style or an implicit style too. The only difference is that if you do choose the implicit style, you now have the extra option of dropping the parentheses ().

const explicitOneLiner = () => { return "something"; };
const implicitOneLiner = () => ("something else");
const anotherImplicitOneLiner = () => "another thing";
Variation 4: Functions that accept a single parameter

Now let's move onto function parameters. The only thing to note here is that parentheses () are required if the function accepts either zero or more than one parameters. On the other hand, dealing with one parameter means that parentheses () are optional.

const noParams = () => "something";
const multipleParams = (x, y) => x + y;

const oneParam = (x) => x * 2;
const anotherOneParam = x => x + 3;

That's it!

In this post, I went over a couple of use cases for arrow functions. But what I covered isn't meant to be an exhaustive list by any means. My aim was to get you to think more in terms of problems and solutions. When you can think in these terms, then you can answer for yourself when is a good time or not to use arrow functions. Hopefully, this post took away some of the mystique of ES6 arrow functions, and will get you started on using them in an idiomatic way.