DEV Community

Cover image for ES6 Arrow Function Syntax Explained Simply
Dan V
Dan V

Posted on • Updated on

ES6 Arrow Function Syntax Explained Simply

ES6 Arrow Function Syntax Explained Simply

Photo by Ferenc Almasi on Unsplash

Introduction

This is a JavaScript arrow function:

const getUserIds = users => users.map(user => user.id)
Enter fullscreen mode Exit fullscreen mode

If your response to the above code is, "Wait, what?!", then read on! Even if you do understand what's going on, you may still pick up a thing or two along the way.

Some initial questions you may have:

  • What is it doing?!
  • What is it returning (if anything)?
  • What does the arrow mean?
  • What is users doing there?

In this article, I want to go over the basics of arrow functions to help new JavaScript developers understand what is actually going on here.

A quick bit of history

ES6 was the 6th Edition of JavaScript released in 2015. It is also known as "ECMAScript 6" or "EMCAScript2015". ES6 was a major revision of JavaScript, and introduced new syntax to help developers write complex code in a simpler form.

One of these new additions was arrow function syntax. Arrow function syntax (or just "arrow syntax") provides a different method of writing functions, in a way that is often shorter and mostly quicker to write and comprehend once you've grasped how the syntax works.

"Normal" functions vs arrow functions

Here is a simple function declaration in basic JavaScript:

function times10(number) {
  return number * 10;
}
Enter fullscreen mode Exit fullscreen mode

Here is the same thing as an arrow function using ES6 arrow syntax:

const times10 = number => number * 10;
Enter fullscreen mode Exit fullscreen mode

Let's pick out the main, visible differences in the arrow function:

  • There are no curly braces ({})
  • There are no parentheses (or "brackets") around the function parameter (i.e. = user =>)
  • Arguably, the syntax is a bit more streamlined.

Note that I use the word "streamlined", and not "better" or "simpler".

How is this useful?

Perhaps you're not convinced that the use of arrow syntax in the above example is providing anything much more useful than the "normal" way of writing functions. In this case, I'd tend to agree. However, arrow functions begin to show their use in more complex scenarios.

Take the below function, which gets ids from an array of user data:

getUserIds(users) {
  return users.map(function(user) {
    return user.id;
  }
}
Enter fullscreen mode Exit fullscreen mode

Using arrow syntax, we can choose to write this function as follows:

const getUserIds = users => users.map(user => user.id);
Enter fullscreen mode Exit fullscreen mode

In some ways, you may find the arrow function more readable and probably quicker write too. Try writing each function in a code editor and see how they compare.

Now, let's break down what is happening in the arrow function's single line of code:

  • We are declaring a variable named getUserIds.
  • The value of getUserIds is a function definition.
  • That function takes an argument named users.
  • The function uses a JavaScript array function called map() to iterate over each item in the users array and return a new array containing only the user ids.
  • The function returns the array that was returned by the map() function.

All on one line.

How did we get here?

Arrow function syntax is flexible, and we could have written this function in a few different ways, including being more verbose with our syntax.

For example, each of these functions when called would map over the users array and return an array of user ids:

// ✔️
const getUserIds1 = (users) => {
  return users.map((user) => {
    return user.id;
  });
}

// ✔️
const getUserIds2 = users => {
  return users.map(user => {
    return user.id;
  });
}

// ✔️
const getUserIds3 = users => users.map(user => {
  return user.id
})

// ✔️
const getUserIds4 = users => users.map(user => user.id)
Enter fullscreen mode Exit fullscreen mode

In the second example, we were able to remove the parentheses from around the users and user parameters, and it works exactly the same.

Why do this? Arguably: for simplicity, though it is entirely optional. Note that this only works for functions with a single parameter.

In the third and fourth examples, we stripped the syntax down further, removing the braces ({}) and the return keywords from the getUserIds() function and then the map() function. This is called implicit return.

Multiple parameters

If your function has multiple parameters (takes multiple arguments), you must use parentheses:

❌ Will throw a syntax error:

const getUserIds = users, prefix => {
  return users.map(user => prefix + user.id);
};
Enter fullscreen mode Exit fullscreen mode

✔️ This is fine:

const getUserIds = (users, prefix) => {
  return users.map(user => prefix + user.id);
};
Enter fullscreen mode Exit fullscreen mode

Argument destructuring

You must however always use parentheses when using argument destructuring:

❌ Will throw a syntax error:

const user = { id: 1, name: "Daniel" }
const getName = { name } => name;
getName(user);
Enter fullscreen mode Exit fullscreen mode

✔️ This is fine:

const user = { id: 1, name: "Daniel" }
const getName = ({ name }) => name;
getName(user)
Enter fullscreen mode Exit fullscreen mode

Implicit return

Arrow functions return the last value returned within it's own scope by default. Note that you should not use the return keyword when writing an arrow function without braces.

Here are some examples of returning (or not) from arrow functions:

Here is our users data:

const users = [{id: 1, name: "Aaron"}, {id: 2, name: "Maya"}]
Enter fullscreen mode Exit fullscreen mode

❌ Using return without braces:

const getUserIds = (users) => return users.map(user => user.id)
                                   ^^^^^^

Uncaught SyntaxError: Unexpected token 'return'
Enter fullscreen mode Exit fullscreen mode

✔️ Implicit return:

const getUserIds = (users) => users.map(user => user.id)

getUserIds(users)

// [1, 2]
Enter fullscreen mode Exit fullscreen mode

✔️ Explicit return:

const getUserIds = (users) => {
  return users.map(user => user.id);
}

getUserIds(users)

// [1, 2]
Enter fullscreen mode Exit fullscreen mode

✔️ Explicitly returning nothing (or undefined, to be precise):

const getUserIds = (users) => {
  users.map(user => user.id);
}

getUserIds(users)

// undefined
Enter fullscreen mode Exit fullscreen mode

You may have expected that last example to return [1, 2], as that is what map() returns. However, the lack of a return keyword means we are not returning the return value of map(). While map() returns a value, we have not set up getUserIds to explicitly or implicitly return that value. Hence, the return value of getUserIds is undefined.

Anonymous functions

We can also use arrow syntax to write anonymous functions. I won't go in-depth on anonymous functions here, but this is what they look like in regular and arrow syntax:

Anonymous function declaration:

function(x, y) { 
  return x + y;
}
Enter fullscreen mode Exit fullscreen mode

Anonymous function expression (implicit return):

function(x, y) { x + y }
Enter fullscreen mode Exit fullscreen mode

Anonymous arrow functions:

(x, y) => x + y;
// Returns x plus y

(x) => x * 100;
// Returns x times 100

x => x
// Returns x

x => {
  return x;
}
// Returns x

const z = 99;
() => z + 1;
// Returns 100;
Enter fullscreen mode Exit fullscreen mode

Ok, but what does the arrow mean?!

The arrow is characters which form syntax which Node or the browser will recognise, just like === or . or +.

It says: "and now I'm going to tell you what this function does".

A nice way to think about it semantically is picturing the arrow as the conveyer belt which moves the arguments through the function.

const add = (a, b) => a + b;
// Take these things, (a,b), and move them through 
// in this direction => into the function, leaving 
// the result on the other side.
Enter fullscreen mode Exit fullscreen mode

Some caveats

Arrow functions are not that different from regular functions. Most of time, you can use them interchangeably without fear of consequence.

However, there are a few technical points you should be aware of when using arrow functions.

No function hoisting

Functions written using the function keyword are "hoisted" at runtime. In simple terms, this means the engine running your code (i.e. Node) will take these functions and store them in memory before executing the rest of your code.

✔️ So you can do this:

multiply(5, 2)
// 10

function multiply(x, y) {
  return x * y;
}
Enter fullscreen mode Exit fullscreen mode

We've written our code in a way where we're calling multiply() before defining what multiply() is.

But because we've used the function keyword, at runtime multiply() will hoisted; it will be read and stored in memory (defined) before the line multiply(5, 2) is executed.

❌ But you can't do this:

multiply(5, 2) // TypeError: multiply is not a function

const multiply = (x, y) => {
  return x * y;
}
Enter fullscreen mode Exit fullscreen mode

Because we've used arrow syntax, multiply() has not been hoisted. So when the runtime engine gets to multiply(5, 2), it sees that multiply() is not defined at this point in the execution and throws an error.

No this

Arrow functions do not have their own this. This is perhaps best explained with examples.

✔️ Using a normal function to access this:

const myObject1 = {
  x: 10,
  getX: function () {
    return this.x;
  }
};

console.log(myObject1.getX());
// 10
Enter fullscreen mode Exit fullscreen mode

❌ Using an arrow function to access this:

const myObject2 = {
  x: 10,
  getX: () => this.x
};

console.log(myObject2.getX());
// TypeError: Cannot read property 'x' of undefined
Enter fullscreen mode Exit fullscreen mode

Other caveats

This article is to help you understand the basics of arrow syntax, but it's helpful to be aware (even if you don't understand in detail) that there are some other technical differences with arrow functions. MDN Web Docs has a good rundown of all the differences, if you are interested in further research.

Are arrow functions better?

Not necessarily. Arrow function syntax provides developers with a tool for writing code in a different way, often with the benefits of code that is easier to read and write.

When ES6 was still fancy and new, arrow functions were seen by some as the "new" way of writing JavaScript functions. Bootcamps and online tutorials would sometimes teach arrow functions by default, and often still do.

But these days, I'm seeing a trend towards bringing it back to the function keyword. Kent C. Dodds, a well-renowned React expert, posted an article about how he uses different function forms for different purposes, which makes for interesting reading.

In Conclusion

ES6 arrow function syntax provides a useful way to write more streamlined code which is often more readable. Code readability is important for helping others to understand your code. Likewise, it's important to be able to read others' code well yourself. So regardless of how you like to write functions, it is good to be able to understand different syntaxes when you encounter them.

I hope this article has been useful for those of you new to writing JavaScript code. If you have any questions, comments, suggestions, or indeed corrections, please let me know in the comments below. I would love to hear your thoughts, whether this article was helpful, and any constructive criticism.

Please feel free to follow me here and on Twitter (@dan_j_v).

Top comments (8)

Collapse
 
taufik_nurrohman profile image
Taufik Nurrohman • Edited

ES5 function scope equivalent to ES6 arrow function:

console.log(this); // `window`

// ES6
button.addEventListener('click', e => {
    console.log(this); // `window`
});

// ES5
button.addEventListener('click', (function (e) {
    console.log(this); // `window`
}).bind(this));
Enter fullscreen mode Exit fullscreen mode
Collapse
 
lsimonis profile image
Lukas Simonis • Edited

It's not quite accurate to say "Arrow functions do not have access to this."

Rather, they inherit the this object of the calling scope:

class C {
  x = 10;
  o = {
    x: 20,
    functionGetX: function () { return this.x; },
    arrowGetX: () => this.x,
  };
}
(new C).o.functionGetX(); // 20
(new C).o.arrowGetX();    // 10
Enter fullscreen mode Exit fullscreen mode
Collapse
 
dan_v profile image
Dan V

Thanks for the clarification @lsimonis . For now, I've updated the article to say they "do not have their own this". :)

Collapse
 
amt8u profile image
amt8u

About hoisiting for arrow functions: Arrow functions are not defined as function declarations. Instead they are always assigned as function expression to a variable.

Thus the regular rule for variable hoisting(let, const and var) comes into picture. It has nothing to do with arrow function syntax.

Collapse
 
dan_v profile image
Dan V • Edited

I updated the "How did we get here?" section code block slightly to show a more granular progression from maximum to minimum verbosity.

Thanks for all the hearts, unicorns, saves, views, and comments so far! I really appreciate it.

Collapse
 
dan_v profile image
Dan V • Edited

Also added a bit more of an explanation as to why the last example in the "Implicit return" section returns undefined and not the return value of the map(). This topic is a bit out of scope of this particular article, but I thought it might be useful to some.

Collapse
 
francescaansell profile image
Francesca Ansell

Very helpful article!

Collapse
 
dan_v profile image
Dan V

Thanks so much!