Arrow Functions in JavaScript
Arrow functions are a new way of writing a traditional function. This way the function is shorter and quicker to write. Arrow functions were introduced in ES6.
Let's see how we write functions in a traditional way:
const sum = function(a, b) {
return a + b;
}
Now let's transform this traditional function into an arrow function:
const sum = (a, b) => {
return a + b;
}
So what do you notice? The function
keyword is removed and an arrow =>
is added for the expression.
We can simplify the above example a little more by removing the return
statement:
const sum = (a, b) => a + b;
Do you notice how the curly braces and return
is gone? Remember, that you can only do this if the first line of the function body is a return
statement.
In the above example, we have two parameters a
and b
. But what if there was only one parameter? Then we can make it even shorter:
const income = salary => salary * 10;
We have removed the parenthesis as well because there was only one parameter salary
. Remember that we can't do this if there is no parameter or parameters are more than one.
Let's do the sorting of numbers with both traditional and arrow functions:
// Traditional style
const numbers = [36, 12, 16, 22];
numbers.sort(function(a, b) {
return a - b;
});
console.log(numbers); // [12, 16, 22, 36]
Let's do the same example with the arrow function:
COPY
COPY
// Arrow function
const numbers = [36, 12, 16, 22];
numbers.sort((a, b) => a - b);
console.log(numbers); // [12, 16, 22, 36]
That's short and simple. ❤
You might want to watch out while returning an object in an arrow function. You can't do it like this:
COPY
COPY
// won't work ❌
const profile = () => { name: 'Dovahkiin', address: 'The Cloud District, Whiterun' };
The correct way is, by wrapping the object in parenthesis like this:
COPY
COPY
// it works ✔
const profile = () => ({ name: 'Dovahkiin', address: 'The Cloud District, Whiterun' });
That's the cosmetic difference between our good old traditional function declaration and the new arrow function. But there is more to it than these fancy changes. ⚡
Let's talk about this
Traditional functions have their own this
binding while arrow functions don't. Arrow functions can only access this
from their parent scope. So if you use this
in an arrow function then it will refer to the object of that arrow function's parent.
Let's understand this by an example:
COPY
COPY
window.age = 130;
function EdwardCullen() {
this.age = 17;
setTimeout(function () {
// this.age is not declared in this scope so it defaults to the window scope
console.log(`Edward Cullen is ${this.age} old.`); // "130"
}, 1);
}
const vampire = new EdwardCullen();
In the above example, we have set the age to 130
on window
object. Now inside the EdwardCullen
we have setTimeout
declared in a traditional way by using function()
.
Now if you try to print this.age
in setTimeout
, you will get 130
which was declared directly as window
object.
Do you know why? Because setTimeout
does not have it's own this.age
declared inside it. So it fetches it by default from the window
object. It doesn't care about this.age
declared in the parent scope.
Let's see the above example using the arrow function:
COPY
COPY
window.age = 130;
function EdwardCullen() {
this.age = 17;
setTimeout(() => {
// there is no `this` scope in arrow function so it takes this.age from it's parent scope
console.log(`Edward Cullen is ${this.age} old.`); // "17"
}, 1);
}
const vampire = new EdwardCullen();
Now this.age
inside setTimeout
prints 17
instead of printing the age from window
scope. Our arrow function setTimeout
tries to find this
in the immediate lexical scope of the parent rather than looking in the window
scope.
This eliminates the confusion and issues with traditional function declaration where this
might work in an unexpected and confusing way.
Let's take another example to understand this:
COPY
COPY
// Traditional way
function EdwardCullen() {
this.age = 17;
this.realAge = function (age) {
this.age = age;
setTimeout(function () {
console.log(this.age); // undefined
}, 1);
};
}
const vampire = new EdwardCullen();
vampire.realAge(120);
The above example prints undefined
because this.age
is not defined inside setTimeout
function. Our traditional function, expects it to be defined in the function itself, if not then it seeks that information in window
object. Here we don't have age
in window
object either. So it prints undefined
.
Can we fix this without using an arrow function? Let's see:
COPY
COPY
function EdwardCullen() {
this.age = 17;
this.realAge = function (age) {
this.age = age;
const self = this;
setTimeout(function () {
console.log(self.age); 120
}, 1);
};
}
const vampire = new EdwardCullen();
vampire.realAge(120);
Do you notice let self = this
? This way we can store this
in another variable named self
and then we can use self.age
inside setTimeout
instead of using this
. Now it prints 120
correctly. This is how we used to solve this problem before arrow functions.
Well, we have the arrow functions now so we don't have to do this little hack any more:
COPY
COPY
function EdwardCullen() {
this.age = 17;
this.realAge = function (age) {
this.age = age;
setTimeout(() => console.log(this.age); 120, 1);
};
}
let vampire = new EdwardCullen();
vampire.realAge(120);
Here we changed the traditional setTimeout
function to an arrow function and it printed 120
correctly. We didn't have to do the self
hack to achieve this. It worked because the arrow function got no binding for this
for itself so it tries to find the binding from the parent function which is declared as 120
in age
parameter of realAge
method.
Limitations of Arrow functions
Arrow functions remove the complexity of this
but that comes with some limitations:
1. Shouldn't be used as methods
It is not recommended to use arrow functions as methods because of the unexpected results. Let's understand this by an example:
COPY
COPY
const edwardCullen = {
age: 17,
getAge: function() {
console.log(this.age);
},
getRealAge: () => console.log(this.age),
}
edwardCullen.getAge(); // 17
edwardCullen.getRealAge(); // undefined
As you can see in the above example, getAge
is using the window object with normal function so it prints 10
while the getRealAge
function has no binding and no near lexical scope inside edwardCullen
so it will print undefined
In this, particular case, we should stick with getAge
by using traditional function declaration. Arrow functions are not a good fit for such cases as evident in the above example.
2. apply
, bind
and call
shouldn't be used
apply
,bind
, and call
work as expected with traditional functions, because we establish the scope for each of the methods but it's not the case with arrow functions.
Let's take a look at the traditional way first:
COPY
COPY
// Traditional function
const edwardCullen = {
age: 17
};
window.age = 120;
const realAge = function(a, b, c) {
return this.age + a + b + c;
}
// call
const firstGuess = realAge.call(edwardCullen, 10, 20, 30);
console.log(firstGuess); // 77
// apply
const secondGuess = realAge.apply(edwardCullen, [10, 20, 30]);
console.log(secondGuess); // 77
// bind
const thirdGuess = realAge.bind(edwardCullen);
console.log(thirdGuess(10, 20, 30)); // 77
We get the age 77
if we use the traditional function.
COPY
COPY
// Arrow function
const edwardCullen = {
age: 17
};
window.age = 120;
const realAge = (a, b, c) => this.age + a + b + c;
// call
const firstGuess = realAge.call(edwardCullen, 10, 20, 30);
console.log(firstGuess); // 180
// apply
const secondGuess = realAge.apply(edwardCullen, [10, 20, 30]);
console.log(secondGuess); // 180
// bind
const thirdGuess = realAge.bind(edwardCullen);
console.log(thirdGuess(10, 20, 30)); // 180
We get the age 180
if we use the arrow function. 😁
Now you can see the difference and why it is suggested NOT to use apply
, bind
and call
with arrow functions.
3. Can not be used as constructors
Arrow functions cannot be used as constructors and they don't have prototype
property as well:
COPY
COPY
const nightCreature = () => 'Edward Cullen';
const vampire = new nightCreature(); // TypeError: nightCreature is not a constructor
COPY
COPY
const nightCreature = () => 'Edward Cullen';
console.log(nightCreature.prototype); // undefined
I hope you learned something today!
Stay awesome! 🌟