3 Examples of When Not to Use JavaScript Arrow Functions

Jason Brewer
4 min readJan 2, 2021

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. The this value of the enclosing lexical scope is used; arrow functions follow the normal variable lookup rules. So while searching for this which is not present in current scope, an arrow function ends up finding the this 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.

--

--

Jason Brewer

Software Engineer @ DELL | Teacher/Mentor @ Devslopes "learn to code" | Writer | Tech Enthusiast