Arrow functions in JavaScript use a concise syntax to create function expressions. JavaScript introduced it as part of ES6 specifications. This article walks you through what Arrow function is, how to create it, and some of its use cases and pitfalls. We also look at how arrow function resolves this
and how it is different from a regular JavaScript function.
Table of Contents
What is Arrow Function
Arrow functions in JavaScript allow us to use the compact syntax to create the function expression. It uses a “fat arrow” operator, => to separate the function parameters from the function body.
1 2 3 4 5 6 7 | const sayHello = (name) => { return "Hello, "+name; }; greet('Paul'); // => 'Hello, Paul |
It is similar to the lambda expressions, which you see in C#, python & Java.
Similar to function expressions, you cannot call an arrow function before declaring it. They are not hoisted like a regular function declaration.
You cannot name an arrow function. They are always anonymous
But the most important distinction between a regular function and an arrow function is how JavaScript resolves this
. It resolves it lexically and we will see that using an Example
How to Create a Arrow Function
Take a regular function expression.
1 2 3 4 5 | sum = function (a,b) { return a+b; } |
To convert it into an arrow function
- Remove the function keyword.
- Place a
=>
before curly braces
1 2 3 4 5 6 7 8 9 10 | //Arrow function sum = (a,b) => { return a+b; } sum(10,10) //20 |
The sum
is an arrow function. You can invoke it just like a regular function. There’s no difference there.
If the function contains a single return statement then, then you can omit the curly braces & return statement.
1 2 3 | sum = (a,b) => a+b |
But you need to use the curly braces, if it contains more than one statement.
1 2 3 4 5 6 7 | sum = (a,b) => { let c= a+b ; //more statements return c; } |
If the arrow function has only one argument, then you can omit the parenthesis
1 2 3 4 5 6 7 8 9 10 11 12 | sqr = a => a*a //or multiline sum = a => { let c= a+10 ; //more statements return c; } |
But if it has no parameter, then a we must add empty parenthesis
1 2 3 | sqr = () => 10*10 |
Arrow Function Examples
Here is some of the basic examples of the arrow function
1 2 3 4 5 | sayHello = (name) => "Hello " + name; sayHello("Bill") //Hello Bill |
1 2 3 4 5 6 7 8 9 | const power = (base, exponent) => { let result = 1; for (let count = 0; count < exponent; count++) { result *= base; } return result; }; |
1 2 3 4 5 6 7 8 9 | const numbers = [2, 4, 6, 8]; const doubled = numbers.map((number) => { return number * 2; }); console.log(doubled); // => [4, 8, 12, 16] |
This and Arrow Function
Arrow functions do not define their own this
.
In the scopes in the JavaScript tutorial, we learned that if a function tries to access a variable that is not declared by it, it will look for it in its parent scope. If it does not find it there it will continue to search its parent until it reaches to root scope. This is called Lexical scope.
This is exactly what happens when we try to access this
in an arrow function. Since the arrow function does not have a this
variable, It will look for it in the enclosing scope. (i.e. outer function if it is inside a function or global if it not inside a function). i.e it resolves this
lexically.
Consider the following example.
The obj
object has a multiply method. It multiplies each number from the numbers
array by a this.factor
and returns a new numbers
array. obj
declares factor
as the local variable with a value of 2. We also have a global variable with the same name and with a value of 5.
What is the value of this
inside the function ?. Run the code and you will realize that it is a global variable and not from the obj.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | Obj = { factor: 2, multiply: function (numbers) { console.log(this.factor) //2 numbers = numbers.map( //function expression function (number) { return number * this.factor; } ); return numbers; } } var factor = 5 const numbers = [1, 2, 3, 4]; console.log(Obj.multiply(numbers)) //5, 10, 15, 20 |
The function expression always declares a this
variable. It is given to it by JavaScript when we invoke the function expression (not when we declare it). And its value depends on how we invoke it. Since it is invoked as a standalone function, its this
points to the global object. That is why its this.factor
value is 5.
Now let us change the example and use the arrow function.
The arrow function does not declare this
. If you try to access this
it always looks for it in the parent scope. The parent scope comes from the multiply
function. We invoke it using method invocation using Obj
. ( Obj.multiply(numbers)
). Hence Obj
becomes the multiply functions this
. So Obj
also becomes the this
of the arrow function
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | Obj = { factor: 2, multiply: function (numbers) { console.log(this.factor) // 2 numbers = numbers.map( //arrow function (number) => { return number * this.factor; } ); return numbers; } } var factor = 5 const numbers = [1, 2, 3, 4]; console.log(Obj.multiply(numbers)) //2, 4, 6, 8 |
Now, let us change the code a little bit. Let us assign the Obj.multiply
to another variable ob
and invoke it.
Since ob
invoked as a standalone function, the global object becomes the this
of multiply function. So it also becomes the this
of arrow function.
1 2 3 4 | let ob = Obj.multiply console.log(ob(numbers)); //5, 10, 15, 20 |
We cannot change the this
Arrow functions do not define their own this
. Hence you cannot modify something that does not exist. There is no way we can change that.
In the following example, foo returns an arrow function. Hence it is lexically bound to foo
. Whatever is the this
of foo
also becomes this
of arrow function
1 2 3 4 5 6 7 8 9 10 11 12 13 | a=1 function foo() { return () => { console.log(this.a); }; } //this of foo is global object b=foo(); b() //1 |
Continuing from the above example, we change the this
of foo
to obj1
using the call
method. The arrow function will use the obj1
as it’s this and prints 10
1 2 3 4 5 6 7 8 9 | //Here we change the this of foo to obj1 //using call method obj1 = { a:10 } b = foo.call(obj1) b(); //10 |
Here, we try to change the this
of arrow function to obj2
using the call
method. But this does not work and the arrow function still uses the this from the foo
, which is still obj1
1 2 3 4 5 6 7 | //Changing the this of arrow function to obj2 //This does not work obj2= { a:100 } b.call(obj2); //10 not 100 |
No constructor or prototype
We cannot use the arrow function as a constructor neither it has a prototype property.
To become a constructor function, the function must declare this
. Since there is no this
in arrow function, we cannot use it as a constructor function. Since we cannot use them as constructors, there is no need for the prototype
property either.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | normalAdd=function(a,b) { return a+b } arrowAdd = (a,b) => { return a+b } console.log(normalAdd.prototype) //{constructor: ƒ} console.log(arrowAdd.prototype) //undefined x=new normalAdd(); //no error y=new arrowAdd(); //Uncaught TypeError: arrowAdd is not a constructor |
No arguments Object
Unlike regular functions, the arrow function also does not have a arguments object.
For Example, you can access the arguments using an Array-like arguments
objects. This object is automatically added by JavaScript on function invocation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | function addNumbers(a, b, c) { console.log(arguments[0]); //10 console.log(arguments[1]); //20 console.log(arguments[2]); //30 console.log(arguments[3]); //undefined console.log(arguments[4]); //undefined return a+b+c; } addNumbers(10, 20, 30); |
But arrow functions do not have arguments object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | addNumbers = (a, b, c) => { console.log(arguments[0]); //Uncaught ReferenceError: arguments is not defined console.log(arguments[1]); // console.log(arguments[2]); // console.log(arguments[3]); // console.log(arguments[4]); // return a+b+c; } addNumbers(10, 20, 30); |
But that is not a big deal as you can make use of rest parameters (...arguments
).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | addNumbers = (...arguments) => { console.log(arguments[0]); //10 console.log(arguments[1]); //20 console.log(arguments[2]); //30 console.log(arguments[3]); //undefined console.log(arguments[4]); //undefined } addNumbers(10, 20, 30); |
Arrow functions pitfalls
Because the arrow function resolves this
lexically, there are few circumstances where you need to be careful before using them. As a rule of thumb, ensure that you double-check your code, if you are using this
in an arrow function.
The following are some of the examples, where you can go wrong.
As Methods of the object
In the following example. sayHello
returns undefined for firstName
& lastName
becuase this here this
is a global object and not person
object.
While sayHello1
works correctly, because it is a method invocation (person.sayHello1()) Hence the person object is the value of this
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // Whatever `this` is here... var person = { firstName: 'Bill', lastName:'Jackson', sayHello: () => { // ...is what `this` is here. console.log('Hello '+this.firstName+ ' '+this.lastName); }, sayHello1: function() { //we invoked using person.sayHelllo1(). Hence the person is `this` here console.log('Hello '+this.firstName+ ' '+this.lastName); } }; person.sayHello(); //Hello undefined undefined person.sayHello1(); //Hello Bill Jackson |
As Event Handlers
In EventHandler
callback function, every browser sets this
to the element which fired the event. Since you cannot change the this
of arrow function, you cannot use it here.
For Example, the following code does not work, because this
inside the arrow function is global object and not username
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <head> </head> <body> <h1>Javascript Arrow function Example</h1> <input type="text" name="username" id="username" placeholder="Enter a username"> <div id="greeting"></div> <script> const greeting = document.querySelector('#greeting'); const username = document.querySelector('#username'); username.addEventListener('keyup', () => { //browser cannot inject 'this' becuase arrow function does not allow to change the 'this' //'this' comes from the global object //Hence 'value' is undefined greeting.textContent = 'Hello ' + this.value; }); </script> </body> |
But the regular function works, because this
here points to username
and it is set by the browser.
1 2 3 4 5 6 7 | username.addEventListener('keyup', function () { //Browser injects 'userName' as 'this' here greeting.textContent = 'Hello ' + this.value; }); |