All About JavaScript Arrow Functions

Arrow functions offer a concise alternative for standard functions. Though, there are important functionality differences regarding this binding, constructors, generators, and more.

Published
Aug 08, 20

Developer, dog lover, and burrito eater. Currently teaching AdonisJS, a fully featured NodeJS framework, and running Adocasts where I post new lessons weekly. Professionally, I work with JavaScript, .Net C#, and SQL Server.

Adocasts

Burlington, KY

A common misunderstanding with arrow functions is that they're merely "syntactical sugar" for regular functions. In actuality, arrow functions bring their own benefits over standard functions in addition to making it easy to write one-liners. We'll be taking a look at the syntactical sugar aspects of arrow functions as well as a few of the more prominent functionality differences between arrow functions and standard functions.

What is an Arrow Function?

Arrow functions, as defined by the MDN Web Group, "is a syntactically compact alternative to a regular function expression, although without its own bindings to the this, arguments, super, or new.target keywords. Arrow function expressions are ill suited as methods, and they cannot be used as constructors."

MDN packs a bunch into that brief definition, and if you found it hard to digest don't worry. We're going to spend some time here unpacking that definition so you walk away fully comprehending what an arrow function is, when to use one, and when not to use one.

The Syntactical Sugar

For those brand new to arrow functions, let's go over exactly what an arrow function looks like compared to the standard function.

const standardFunction = function () {
  console.log('standard: do something here');
}

const arrowFunction = () => {
  console.log('arrow: do something here');	
}

standardFunction();
arrowFunction();
Copied!

Your first thought might be, well that's not very compact compared to the standard function, and you're absolutely right! When we use arrow functions like a standard function, they look almost identical. The main difference being you drop the function keyword and you add an arrow (=>) just behind the parenthesis.

This, however, only scratch the surface of the arrow function's syntactical sugar capabilities.

Let's Kick It Up A Notch

So we've covered that arrow functions allow us to drop the function keyword in favor of an arrow (=>), however, that's not the only keyword they allow us to drop. Arrow functions give us the ability to omit curly braces and the return keyword when we have a single-line arrow function. It's important to note that in order to omit the return keyword you also need to omit the curly braces. It's a packaged deal. Even if your arrow function is on a single line, if you have curly braces, in order to return a value you'll need to include the return keyword.

So let's incrementally adopt this single-line arrow function via a series of examples.

// 1. same arrow function structure as before
const exampleOne = (someText) => {
  console.log('this is', someText);
}

// 2. since we have a single parameter, we can optionally drop the parenthesis
const exampleTwo = someText => {
  console.log('this is', someText);
}

// 3. since we have a single line we can optionally drop the curly braces
const exampleThree = someText => console.log('this is', someText);

// 4. since we dropped the curly braces, whatever follows the => is returned
const exampleFour = someText => 'this is ' + someText;

// 5. we can add back the parenthesis at any time
const exampleFive = (someText) => 'this is ' + someText;

// 6. but if we add back the curly braces, we must add return to return a value
const exampleSix = (someText) => { return 'this is ' + someText; }

exampleOne('example one');
exampleTwo('example two');
exampleThree('example three');

console.log(exampleFour('example four'));
console.log(exampleFive('example five'));
console.log(exampleSix('example six'));
Copied!

I'd recommend copy/pasting this into your browser's console and inspecting the results. Once you've done that return back here and let's step through these examples.

  1. Our first example is the same structure as our last arrow function example. We need a place to start from.

  2. In example two, since our function only accepts one parameter, we can optionally remove the parenthesis around the parameter name. Note that if we were to add a second parameter we would need to put the parenthesis back otherwise JavaScript will throw an error.

  3. With example three things take a jump forward. Since we only have a single line in our function, we can omit the curly braces and move our line up to directly after our arrow (⇒). It's important to note that since we have omitted the curly braces from our arrow function, it will now return the result of whatever our single-line is once the function is run. In this case, since console.log() returns undefined so does our function.

  4. Now that we now from example three that our arrow function, without curly braces, will automatically returns it's result, we can use this to our advantage. Let's move our console.log around our exampleFour function call. This will log out whatever is returned from exampleFour when it's called. In this case it'll be, "this is example four."

  5. Note that at any point we are free to add back the parenthesis around our parameter.

  6. However, if we add back the curly braces we will lose the automatic return for our arrow function. Therefore, we would need to explicitly tell the function to return a value using the return keyword.

Practical Example

So we've already covered a lot, and we haven't even gotten to the functionality of arrow functions yet. First I want to take a second from concatenating strings to give you a practical example of when an arrow function might make a lot of sense.

Grabbing a single field out of a collection

// Task -------------------------
// find the Matt Groening show, 
// Futurama by it's id, 2.
// ------------------------------

// we have a collection (array of objects) that have an id and a title
const collection = [
  { id: 1, title: 'The Simpsons' },
  { id: 2, title: 'Futurama' },
  { id: 3, title: 'Disenchantment' }
];

// using a standard function
const standardFuturama = collection.find(function(show) {
  return show.id == 2;
});

// using an arrow function
const arrowFuturama = collection.find(show => show.id == 2);

console.log(standardFuturama);
console.log(arrowFuturama);
Copied!

We can see through this example using the find array method where those omissions really start to become beneficial in our everyday code.

What About Returning an Object?

Your first instinct to return an object using arrow functions, might be business as normal, like the below.

const makeShow = (title) => { id: Date.now(), title: title }
makeShow('The Simpsons');
Copied!

However, it's important to realize we're still working with a function. While our intention was to return an object, what we've really done is added curly braces to our arrow function. Meaning, JavaScript attempts to use id: Date.now(), title: title as a function block statement.

To get around this, we can wrap our object with parenthesis. This will tell JavaScript that we aren't using a block function (a function with curly braces), and it'll return the result of what's inside our parenthesis.

const makeShow = (title) => ({ id: Date.now(), title: title })
makeShow('Futurama');
Copied!

The Functionality

Now before we use arrow functions within our code, it's imperative that we also understand some of the functionality impacts it has compared to the standard function.

This

Everyone's favorite keyword in JavaScript, this, actually isn't bound to arrow functions. In many cases, this becomes a very powerful strength for arrow functions. It's also a very prominent reason in many cases for why you shouldn't use arrow functions.

Let's examine an example where you wouldn't want to use an arrow function because it doesn't get bound to this.

Reason Against, Use-Case

Methods are one use-case where you typically wouldn't want to use an arrow function. A method, just in case you weren't entirely sure, is a function attached to an object.

const item = {
  name: 'T-Shirt',
  color: '#ebebeb',
  rightClick: function () {
    console.log('right click', this);
  },
  leftClick: () => {
    console.log('left click', this);
  }
}

item.rightClick();
item.leftClick();
Copied!

Here, when we call item.rightClick() we have access to the object's this context, meaning we have access to both name and color via this.name and this.color. This is because we use a standard function for the rightClick method, therefore, the object's this is bound to our method.

However, when we call item.leftClick(), since we use a arrow function the object's this isn't bound to the method. Meaning this inside our leftClick method is the window's scope and we won't have access to either this.name nor this.color.

Reason For, Use-Case

One reason why you would want to use an arrow function to bypass this being bound to your function is when you need to access the parent's this within your function.

Let's rework our previous example at a little so that we have more going on inside our click. Imagine we want to perform an animation once the item's rightClick method is called. We'll use two setTimeout calls to animate in and out.

const item = {
  name: 'T-Shirt',
  color: '#ebebeb',
  rightClick: function () {
    console.log('right click', this);
    
    setTimeout(function () {
      console.log('standard function', this);
	  // some animation handler here
    }, 1000);
    
    setTimeout(() => {
      console.log('arrow function', this);
	  // another animation handler here
    }, 2000);
  }
}

item.rightClick();
Copied!

For our first timeout, we use a standard function. As a result, this inside our first timeout is bound to the window instead of our item. Whereas with our second timeout, we use an arrow function. This bypasses the binding, resulting in this remaining the same as and remaining bound to our item.

Arguments

Arrow functions aren't defined an arguments object. Therefore, any usages of arguments within an arrow function will reference any parent-scoped arguments. In cases like the one below, where there isn't a parent-scoped arguments object, a ReferenceError will be thrown.

const standardExample = function (someText) {
  console.log('this is', arguments);
}

const arrowExample = someText => {
  console.log('this is', arguments); 
}

standardExample('standard');
arrowExample('arrow');
Copied!

In this example our standardExample function logs the arguments object, containing "standard" under key 0. Whereas, our arrowExample actually errors out because the arrow function isn't given an arguments object and we also don't have an arguments object within our parent scope.

See the console output for this below.

Others

There are several other functionality changes arrow functions bring with them. These include usages of the new keyword, yield keyword, a lack of a prototype, and a few others.

New Keyword

Arrow functions don't have the ability to be constructors. So, if you need to create a constructor function it's important to reach for a standard function over an arrow function.

const StandardMenuItem = function (title, href) {
  this.title = title;
  this.href = href;
}

const ArrowMenuItem = (title, href) => {
  this.title = title;
  this.href = href;
}

console.log(new StandardMenuItem('standard', '/standard'));
console.log(new ArrowMenuItem('arrow', '/arrow'));
Copied!

When we attempt to use the new keyword to construct a new ArrowMenuItem, the error TypeError: ArrowMenuItem is not a constructor will be thrown.

Yield Keyword

Unless a standard function is nested as a child inside an arrow function, the yield keyword will not work. So, don't use arrow functions for generators.

const data = [
  { id: 1, title: 'The Simpsons' },
  { id: 2, title: 'Futurama' }
];

// standard function works fine
const standardFunction = function* (items) {
  items.forEach(item => {
    yield item.title;
  });
}

// arrow function errors out
const arrowFunction = * (items) {
  items.forEach(item => {
    yield item.title;
  });
}
Copied!

Before we even call our arrowFunction the error SyntaxError: yield expression is only valid in generators will be thrown.

Prototype

The last functionality change we'll be covering regarding arrow functions is it's lack of a prototype. It just doesn't have one.

const standardFunction = function () {};
const arrowFunction = () => {};

console.log('standard', standardFunction.prototype);
console.log('arrow', arrowFunction.prototype);
Copied!

Here the standard function will log out it's prototype, while the arrow function will log out undefined.

What's Next?

I highly recommend playing around with arrow functions. Get comfortable with them. Not only do they become super handy in everyday JavaScript tasks, but they also make life a lot easier when working with popular frameworks, like React and Vue, as well.

We skipped a couple of smaller items regarding arrow functions in this post. If you're curious about the full feature set of arrow functions, be sure to check out MDN Web Group's documentation.

Join The Discussion! (0 Comments)

Please sign in or sign up for free to join in on the dicussion.

robot comment bubble

Be the first to Comment!