DEV Community

Cover image for How and when to use JavaScript arrow functions
Megan Lee for LogRocket

Posted on • Originally published at blog.logrocket.com

How and when to use JavaScript arrow functions

Written by Joe Attardi✏️

The ES2015 standard introduced arrow functions to JavaScript. Arrow functions have a simpler syntax than standard functions, but we’ll also see that there are some important differences in how they behave.

What are JavaScript arrow functions?

Arrow functions can be used almost anywhere a standard function expression can be used, with a few exceptions. They have a compact syntax, and like standard functions, have an argument list, a body, and a possible return value.

We’ll explore arrow functions in detail below, but in general they should be avoided any time you need a new this binding. Arrow functions don’t have their own this; they inherit the this from the outer scope.

Arrow functions also can’t be used as constructors or generator functions, as they can’t contain a yield statement.

What is the basic syntax of a JavaScript arrow function?

An arrow function consists of a list of arguments, followed by an arrow (made with an equals sign and a greater-than sign (=>), followed by the function body. Here’s a simple example of an arrow function that takes a single argument:

const greet = name => {
  console.log(`Hello, ${name}!`);
};
Enter fullscreen mode Exit fullscreen mode

You can optionally also surround the argument with parentheses:

const greet = (name) => {
  console.log(`Hello, ${name}!`);
}
Enter fullscreen mode Exit fullscreen mode

If an arrow function takes more than one argument, the parentheses are required. Like a standard function, the argument names are separated by commas:

const sum = (a, b) => {
  return a + b;
}
Enter fullscreen mode Exit fullscreen mode

An anonymous arrow function has no name. These are typically passed as callback functions:

button.addEventListener('click', event => {
  console.log('You clicked the button!');
});
Enter fullscreen mode Exit fullscreen mode

If your arrow function body is a single statement, you don’t even need the curly braces:

const greet = name => console.log(`Hello, ${name}!`);
Enter fullscreen mode Exit fullscreen mode

Implicit returns and the JavaScript arrow function

One of the important differences between JavaScript arrow functions and standard functions is the idea of an implicit return: returning a value without using a return statement.

If you omit the curly braces from an arrow function, the value of the function body’s expression will be returned from the function without needing a return statement. Let’s revisit the sum function from earlier. This can be rewritten to use an implicit return:

const sum = (a, b) => a + b;
Enter fullscreen mode Exit fullscreen mode

Implicit return is handy when creating callback functions:

const values = [1, 2, 3];
const doubled values = values.map(value => value * 2); // [2, 4, 6]
Enter fullscreen mode Exit fullscreen mode

Returning an object implicitly

You can return any kind of value you want with an implicit return, but you’ll need a little extra help if you want to return an object. Since an object literal uses curly braces, JavaScript will interpret the curly braces as the function body. Consider this example:

const createUser = (name, email) => { name, email };
Enter fullscreen mode Exit fullscreen mode

In this case, there will be no implicit return and the function will actually return undefined because there is no return statement. To return an object implicitly, you need to wrap the object with parentheses:

const createUser = (name, email) => ({ name, email });
Enter fullscreen mode Exit fullscreen mode

Now JavaScript knows this is an implicit return of an object containing the name and email properties.

Explicit returns and the JavaScript arrow function

Like with standard functions, an arrow function can explicitly return a value with a return statement:

const createUser = (name, email) => {
  return { name, email };
};
Enter fullscreen mode Exit fullscreen mode

How JavaScript arrow functions differ from standard functions

Arrow functions behave differently from standard functions in some other ways.

No this binding

The most significant difference is that, unlike a standard function, an arrow function doesn’t create a this binding of its own. Consider the following example:

const counter = {
  value: 0,
  increment: () => {
    this.value += 1;
  }
};
Enter fullscreen mode Exit fullscreen mode

Because the increment method is an arrow function, the this value in the function does not refer to the counter object. Instead, it inherits the outer this, which in this example would be the global window object.

As you might expect, if you call counter.increment(), it won’t change counter.value. Instead, this.value will be undefined since this refers to the window.

Sometimes, you can use this to your advantage. There are cases where you do want the outer this value from within a function. This is a common scenario when using callback functions. Before arrow functions, you’d have to call bind on a function to force it to have a certain this, or you might have followed a pattern like this:

var self = this;
setTimeout(function() {
  console.log(self.name);
}, 1000);
Enter fullscreen mode Exit fullscreen mode

With an arrow function, you get the this from the enclosing scope:

setTimeout(() => console.log(this.name));
Enter fullscreen mode Exit fullscreen mode

No arguments object

In a standard function, you can reference the arguments object to get information about the arguments passed to the function call. This is an array-like object that holds all the argument values. In the past, you might have used this to write a variadic function.

Consider this sum function, which supports a variable number of arguments:

function sum() {
  let total = 0;
  for (let i = 0; i < arguments.length; i++) {
    total += arguments[i];
  }

  return total;
}
Enter fullscreen mode Exit fullscreen mode

You can call sum with any number of arguments:

sum(1, 2, 3) // 6
Enter fullscreen mode Exit fullscreen mode

If you implement sum as an arrow function, there won’t be an arguments object. Instead, you’ll need to use the rest parameter syntax:

const sum = (...args) => {
  let total = 0;
  for (let i = 0; i < args.length; i++) {
    total += args[i];
  }

  return args;
}
Enter fullscreen mode Exit fullscreen mode

You can call this version of the sum function the same way:

sum(1, 2, 3) // 6
Enter fullscreen mode Exit fullscreen mode

This syntax isn’t unique to arrow functions, of course. You can use the rest parameter syntax with standard functions, too. In my experience with modern JavaScript, I don’t really see the arguments object being used anymore, so this distinction may be a moot point.

No prototype

Standard JavaScript functions have a prototype property. Before the introduction of the class syntax, this was the way to create objects with new:

function Greeter() { }
Greeter.prototype.sayHello = function(name) {
  console.log(`Hello, ${name}!`);
};

new Greeter().sayHello('Joe'); // Hello, Joe!
Enter fullscreen mode Exit fullscreen mode

If you try this with an arrow function, you’ll get an error. This is because arrow functions don’t have a prototype:

const Greeter = () => {};
Greeter.prototype.sayHello = name => console.log(`Hello, ${name}!`);
// TypeError: Cannot set properties of undefined (setting 'sayHello')
Enter fullscreen mode Exit fullscreen mode

When to use JavaScript arrow functions vs. standard functions

Arrow functions can be used in a lot of scenarios, but there are some situations where you still need to use a standard function expression. These include:

  • The constructor of a class
  • An object method where you need to access the object via a this value
  • A function that you need to explicitly bind to a given this value with Function.prototype.bind
  • A generator function containing yield statements

Arrow functions particularly shine when used as callback functions, due to their terse syntax. In particular, they are very useful for array methods such as forEach, map, and filter. You can use them as object methods, but only if the method doesn’t try to access the object using this. The arrow function is very useful in certain situations. But like most things, arrow functions have potential pitfalls if you don’t use them correctly.

How to define a method using an arrow function

Here’s how you’d define a method using an arrow function:

class Person {
  constructor(name) {
    this.name = name;
  }

  greet = () => console.log(`Hello, ${this.name}!`);
}
Enter fullscreen mode Exit fullscreen mode

Unlike a method on an object literal — which as we saw earlier does not get the this value — here the greet method gets its this value from the enclosing Person instance. Then, no matter how the method is called, the this value will always be the instance of the class. Consider this example that uses a standard method with setTimeout:

class Person {
  constructor(name) {
    this.name = name;
  }

  greet() {
      console.log(`Hello, ${this.name}!`);
  }

  delayedGreet() {
      setTimeout(this.greet, 1000);
  }
}

new Person('Joe').delayedGreet(); // Hello, undefined!
Enter fullscreen mode Exit fullscreen mode

When the greet method is called from the setTimeout call, its this value becomes the global window object. The name property isn’t defined there, so you’ll get Hello, undefined! when you call the delayedGreet method.

If you define greet as an arrow function instead, it will still have the enclosing this set to the class instance, even when called from setTimeout:

class Person {
    constructor(name) {
      this.name = name;
    }

    greet = () => console.log(`Hello, ${this.name}!`);

    delayedGreet() {
        setTimeout(this.greet, 1000);
    }
}

new Person('Joe').delayedGreet(); // Hello, Joe!
Enter fullscreen mode Exit fullscreen mode

You can’t, however, define the constructor as an arrow function. If you try, you’ll get an error:

class Person {
  constructor = name => {
    this.name = name;
  }
}

// SyntaxError: Classes may not have a field named 'constructor'```
{% endraw %}


## Conclusion

Since the arrival of the ES2015 standard, JavaScript programmers have had arrow functions in their toolbox. Their main strength is the abbreviated syntax; you dont need the {% raw %}`function`{% endraw %} keyword, and with implicit return you dont need a {% raw %}`return`{% endraw %} statement. 

The lack of a {% raw %}`this`{% endraw %} binding can cause confusion, but is also handy when you want to preserve the enclosing {% raw %}`this`{% endraw %} value to another function when passed as a callback. 

Consider this chain of array operations:
{% raw %}


```javascript
const numbers = [1, 2, 3, 4]
  .map(function(n) {
    return n * 3;
  })
  .filter(function(n) {
    return n % 2 === 0;
  });
Enter fullscreen mode Exit fullscreen mode

This looks fine, but it’s a little verbose. With arrow functions, the syntax is cleaner:

const numbers = [1, 2, 3, 4]
  .map(n => n * 3)
  .filter(n => n % 2 === 0);
Enter fullscreen mode Exit fullscreen mode

Arrow functions don’t have an arguments object, but they do support rest parameter syntax. This makes it easy to build arrow functions that take a variable number of arguments.

The main advantages of arrow functions are enhanced readability as well as the different this behavior, which will make life easier in certain situations where you need to preserve an outer this value.


LogRocket: Debug JavaScript errors more easily by understanding the context

Debugging code is always a tedious task. But the more you understand your errors, the easier it is to fix them.

LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to see exactly what the user did that led to an error.

LogRocket Signup

LogRocket records console logs, page load times, stack traces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!

Try it for free.

Top comments (2)

Collapse
 
patzi275 profile image
Patrick Zocli • Edited

Thanks for sharing

Collapse
 
webjose profile image
José Pablo Ramírez Vargas

Good stuff.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.