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.
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.
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}!`); };
You can optionally also surround the argument with parentheses:
const greet = (name) => { console.log(`Hello, ${name}!`); }
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; }
An anonymous arrow function has no name. These are typically passed as callback functions:
button.addEventListener('click', event => { console.log('You clicked the button!'); });
If your arrow function body is a single statement, you don’t even need the curly braces:
const greet = name => console.log(`Hello, ${name}!`);
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;
Implicit return is handy when creating callback functions:
const values = [1, 2, 3]; const doubled values = values.map(value => value * 2); // [2, 4, 6]
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 };
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 });
Now JavaScript knows this is an implicit return of an object containing the name
and email
properties.
Like with standard functions, an arrow function can explicitly return a value with a return
statement:
const createUser = (name, email) => { return { name, email }; };
Arrow functions behave differently from standard functions in some other ways.
this
bindingThe 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; } };
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);
With an arrow function, you get the this
from the enclosing scope:
setTimeout(() => console.log(this.name));
arguments
objectIn 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; }
You can call sum
with any number of arguments:
sum(1, 2, 3) // 6
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; }
You can call this version of the sum
function the same way:
sum(1, 2, 3) // 6
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.
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!
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')
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:
this
valuethis
value with Function.prototype.bind
yield
statementsArrow 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.
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}!`); }
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!
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!
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'
Since the arrival of the ES2015 standard, JavaScript programmers have had arrow functions in their toolbox. Their main strength is the abbreviated syntax; you don’t need the function
keyword, and with implicit return you don’t need a return
statement.
The lack of a this
binding can cause confusion, but is also handy when you want to preserve the enclosing this
value to another function when passed as a callback.
Consider this chain of array operations:
const numbers = [1, 2, 3, 4] .map(function(n) { return n * 3; }) .filter(function(n) { return n % 2 === 0; });
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);
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.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how the call stack, event loop, and various queues help JavaScript handle asynchronous operations while maintaining its single-threaded nature.
null
, undefined
, or empty values in JavaScriptIn most languages, we only have to cater to null. But in JavaScript, we have to cater to both null and undefined. How do we do that?
Discover how the MERN stack (MongoDB, Express.js, React, Node.js) enables developers to build dynamic, performant, modern websites and apps.
Use parallel computing in Node.js with worker threads to optimize performance, handle CPU-intensive tasks, and utilize multi-core processors.