The arrow function was one of the features introduced in the ECMAScript 2015 specification (ES6) of JavaScript.
Arrow functions are a new way to write anonymous function expressions and are lexically scoped, meaning they have access to the enclosing function's variables, which makes them incredibly useful when passing a function as a parameter to a higher-order function, such as when you are looping over an array with built-in iterator methods.
The arrow function notation has several characteristics that differentiate it from traditional function expressions, we will discuss some of them aling with examples in this article.
TLDR;
Using arrow function syntax, the following traditional function expressions:
Can be written like this:
Arrow Function Syntax
Before delving into the examples and specifics of arrow functions, its useful to understand the syntax.
let functionName = (arg1, arg2, ...argN) => {
expression(s)
}
The syntax for an arrow function expression does not require the function
keyword and uses a fat arrow =>
to separate the arguments(s) from the body.
functionName
is a variable holding a refrance to access the functionarg1, arg2, ...argN
the function argumentsexpression(s)
the function body
Arrow functions can be defined with zero or more parameters, on one or more lines. If the body has a single statement or expression, you can make use of the implicit return and write an arrow function as:
let functionName = (arg1, arg2, ...argN) => expression
If there is only a single argument, you can drop the brackets ()
surrounding the arguments and write the arrow function as:
let functionName = arg => expression
Different ways of writing functions
To understand arrow functions, it can be helpful to understand the different ways of writing a function in JavaScript.
Function declaration
A traditional function declaration
creates a named function and is defined with the function keyword, followed by the function name and the parameters in ()
.
Function declarations are hoisted into the execution context before any code is executed, this is why you can access them before the have been declared.
console.log(helloWorld('levi', 'putna'));
function helloWorld(firstName, lastName){
return "Hello " + firstName + ' ' + lastName + '!';
}
Function expression
A function expression
is an anonymous function that can be assigned to a variable or passed as an argument to another function.
A function expression
has almost the same syntax as a function declaration but differs from a function declaration
in that it is not hoisted in the execution context and runs only when encountered in the code.
The above helloWorld
function declaration
can be written as a function expression
like so:
const helloWorld = function (firstName, lastName){
return "Hello " + firstName + ' ' + lastName + '!';
}
console.log(helloWorld('levi', 'putna'));
Arrow function
Arrow function
syntax is similar to function expression
syntax, except that the function
keyword is replaced with a fat arrow =>
, and moved after the argument declarations. In addition, Arrow function
offer some syntactic sugar that allows for more concise and readable code.
Arrow functions
have a few critical functional distinctions in the way they work that distinguish them from the other ways of writing a functions. The most significant functional difference is that arrow functions
do not have their own this
binding or prototype and, therefore, cannot be used as a class constructor.
The above helloWorld
function can be written as an Arrow function
like so:
const helloWorld = (firstName, lastName) => {
return "Hello " + firstName + ' ' + lastName + '!';
}
console.log(helloWorld('levi', 'putna'));
Arrow Function Example
Many developers are familiar with traditional function declarations and expressions. Converting a few of these functions to arrow functions is an excellent way to understand the syntax and how they work.
If we use our `helloWorld` Function declaration above as an example.
function helloWold(firstName, lastName){
return "Hello " + firstName + ' ' + lastName + '!';
}
The basic way to write this function with arrow function syntax is like this.
const helloWorld = (firstName, lastName) => {
return "Hello " + firstName + ' ' + lastName + '!';
}
The body of a traditional function is contained within a block using curly brackets {}
and ends when the code encounters a return
keyword. Arrow functions
introduce some syntactical sugar and implicit return, allowing the omission of the curly brackets {}
and the return keyword when the function body is a simple return statement and can be written like this:
const helloWorld = (firstName, lastName) => "Hello " + firstName + ' ' + lastName + '!';
If there is only a single argument, we can reduce the syntax further by removing the parentheses around the arguments.
const helloWorld = fullName => "Hello " + fullName + '!';
If we want to pass a function as an argument to another function, this is where anonymous functions really shine. The arrow function no longer needs to be associated with a variable, and the arrow function can be written as a parameter of the other parent function.
Lexical this scope
Arrow functions are lexically scoped, meaning that they have access to the enclosing function's variables, including the this
variable. Arrow functions don't have their own bindings to this
.
We will create a class with two methods to highlight how the this
keyword is scoped differently between a normal anonymous expression and an arrow function.
The methods will console.log()
out a message including a name stored in the classes this.name
variable after a few seconds using setTimeout()
.
The setTimeout()
function takes in a callback as a parameter. In the first method, expressionSayHello()
will pass a function expression
into the setTimeout()
function and in the second method, arrowSayHello()
will pass an arrow function
.
class HelloWorld {
constructor(name) {
this.name = name;
}
expressionSayHello() {
setTimeout(function() {
console.log("Hello " + this.name + "!");
}, 2000);
}
arrowSayHello() {
setTimeout(() => {
console.log("Hello " + this.name + "!");
}, 2000);
}
}
If we instantiate the class and call the first expressionSayHello()
method, you will notice that the message is missing the name value stored in this.name
. This is because the scope of the this
keyword for a function expression is defined within the function scope, not the surrounding scope. Function expressions are not lexically scoped and cannot access this
from the surrounding function, instead having there own this
.
Now, if we try this again but use the arrowSayHello()
method that uses the arrow function callback, this time, we will see the name stored in this.name
included in the message. The arrow function did not define its own inner this
variable and had access to the value from the surrounding function.
Limitations
Arrow functions have some limits and can’t be used in all situations:
- They do not have their own bindings to this or super.
- They cannot be used as constructors.
- They cannot use yield, within its body.
- They cannot use the special arguments keyword.
Arrow functions are always unnamed. If the arrow function needs to call itself recursively, use a named function expression instead. You can also assign the arrow function to a variable so it has a name and call that.
let makeGreaterThan = (a, b) => {
console.log(a,b);
if (a > b) {
return a;
} else {
a++;
return makeGreaterThan(a, b);
}
};
console.log(makeGreaterThan(1, 10));