3 Examples of When Not to Use JavaScript Arrow Functions
ES6 Arrow Functions
Arrow functions were introduced as part of the ECMAScript 6 update in 2015 and quickly became one of the most renowned features in modern JavaScript. Utilizing the new token =>
gives it a sublime, syntactic sugar that removed the need for the following:
- the function keyword
- curly brackets
- the return keyword (for one line functions)
This gave arrow functions a very clean and concise syntax and reduced some of the complexity involved in scoping and this
binding.
However, like anything with engineering, there are some tradeoffs to using them. There are a number of situations in which arrow functions are not a good choice and can cause you some trouble. Let’s dive into a few scenario’s where arrow functions are not the best fit.
Object Methods
Let’s say you want to create a method to bind to an object.
const skater = {
trickCount: 0,
kickflip: () => {
this.trickCount++;
}
}
In the example above, if we call skater.kickflip()
, we’d expect the value of skater.trickCount
to increase from 0 to 1. However, as currently written, despite how many times kickflip()
is invoked, the value of trickCount
will never increment.
Why the heck not? Because there is no reference to this
in the current scope!
MDN states:
An arrow function does not have its own
this
. Thethis
value of the enclosing lexical scope is used; arrow functions follow the normal variable lookup rules. So while searching forthis
which is not present in current scope, an arrow function ends up finding thethis
from its enclosing scope.
In this example, the enclosing scope would be the window
object. When we invoke kickflip()
we’re telling the code to increment the value of trickCount
in the window
object. However, the value does not exist so the code does not work.
What we need to do, is use the traditional function syntax which will bind the function’s this
to our skater
object that’s calling the function:
const skater = {
trickCount: 0,
kickflip: function() {
this.trickCount++;
}
}
Object Prototype
Here we have a object and are adding a class method through the Object
prototype:
class Pokemon {
constructor(name, ability) {
this.name = name;
this.ability = ability;
};
};
Pokemon.prototype.attack = () => {
console.log(this === window);
return this.ability;
};
const pikachu = new Pokemon("Pikachu", "Lightning-rod");
pikachu.attack();
On the last line we invoke our function and get the following output:
true
undefined
We created the attack()
prototype function and passed in an “attack ability” for our new Pokemon
object, so why are we getting undefined
?
When creating functions that require context, we must use the regular function syntax in order for this
to be properly bound:
In our console.log()
, (this === window)
returns true
. What this means, is just like in our previous object method example, this is actually referencing the window
object. To fix this, we would make the following refactor:
Pokemon.prototype.attack = function() {
console.log(this === pikachu); // true
return this.ability;
};
Dynamic Context
Take a moment to really look at the following code and reflect on everything we have talked about thus far.
const button = document.querySelector(#darkMode);
button.addEventListener('click', () => {
this.classList.toggle('expand');
});
Will this code work? In this scenario what should this
to be bound to? The window or the element? And how will the use of an arrow function affect this?
When working with event handlers or event listeners, this
is bound dynamically. If we use an arrow function here, dynamic binding will not work. That’s because when an arrow function is declared, the syntax binds context statically.
When we make DOM manipulations using event handlers or listeners, those triggered events point to the this
belonging to the targeted element.
That means when we use an arrow function in this situation, this
will point to window
. So in the code above, this.classList
will actually evaluate to window.classList
, which is why we get a TypeError
.
Now we could “refactor” our arrow function in this case to make it work by deliberately overriding this default behavior. But if we just need to simply reference the element, a regular function is preferred and is less code to write.
Example with refactored arrow function:
const button = document.querySelector(#darkMode);
button.addEventListener('click', (e) => {
e.currentTarget.classList.toggle('expand');
});
Wrapping up
In summary: Arrow functions are a powerful addition to the JavaScript language and make code more efficient in a number of situations. Just remember, like every other feature, there are tradeoffs. I would advise you to use them as an additional tool in your “tool-belt” and not as a replacement for all functions.
I hope these examples were easy to follow and understand. I would also encourage you to read up some more on this
in JavaScript to help solidify your understanding of when to use or not use arrow functions.
As always, thanks for reading.