DEV Community

Michael Sakhniuk
Michael Sakhniuk

Posted on

Why you should avoid using arrow functions to define class methods

In this article, we'll learn about JavaScript class methods and why we shouldn't use arrow functions in methods definition.

Classes have been introduced to JavaScript as part of ES6. Before that, for create a class we used a function where we were describing a JavaScript object with logic in methods and properties using this notation.

Let’s create a simple class with some methods:

class Pet {
  name = 'dog';

  getName() {
    return this.name;
  }
}
Enter fullscreen mode Exit fullscreen mode

There is only one property name and one method getName in this Pet class. Method definition has been set to default function declaration.

Classes can be created this way, but it wasn't possible in ES5 before. Let’s see how we could create the same class in ES5:

"use strict";
var Pet = (function () {
    function Pet() {
        this.name = 'dog';
    }
    Pet.prototype.getName = function () {
        return this.name;
    };
    return Pet;
}());
Enter fullscreen mode Exit fullscreen mode

Here is how class looks like in ES5 - we’ve create function with property name equals dog. We used this notation to refer to object we going to create. In case when we want to create method we could put it as property to object, but would be much better to add the method to the prototype of our function and we actually did it. It allows us to use getName function like it right inside the object and Inheritance and the Prototype chain helps us here:

const pet = new Pet();
const pet2 = new Pet();

console.log(Pet.prototype.getName === pet.getName); // true

console.log(pet.getName === pet2.getName); // true
Enter fullscreen mode Exit fullscreen mode

As you can see we can get access to the getName method right from instance of the our Pet class. One more important thing is that method created only once. Even if we create 1000 instances of the Pet class this function will be reused in every instance.

The class syntax is simple and it also put all method to prototype automatically. We don’t need to think about as before. But together with classes in ES6 we got arrow functions, the new way how to define and work with classes:

const getDate = () => new Date()
Enter fullscreen mode Exit fullscreen mode

The syntax of arrow functions is cleaner and shorter. They also have different behavior on execution level - they have bound context from definition scope instead of execution scope in default function, which prevents developers from making a common mistake by passing a function as an argument to another method or function, which could lose the context.

With the advent of the popularity of arrow functions, they began to be used everywhere and even as class methods. Let’s add new method to our class:

class Pet {
  name = 'dog';

  getName() {
    return this.name;
  }

  getNameArrow = () => this.name;
}
Enter fullscreen mode Exit fullscreen mode

We’ve defined the getNameArrow arrow function. It’s do completely the same thing and looks even better and cleaner by the way. But what could be wrong here?

First thing you might noticed we use = sign to define a method, which actually means we were define a property. Even VScode highlighted it for us:

Vscode highlighted method as a property
As we remember in ES5 vs ES6 syntax - all properties will be added right to the class instance instead of prototype. Let’s try to proof it. We can pass our code of class via Babel or TypeScript compiler, even online version, and see result:

"use strict";
var Pet = /** @class */ (function () {
    function Pet() {
        this.name = 'dog';
        this.getNameArrow = function () { return this.name; };
    }
    Pet.prototype.getName = function () {
        return this.name;
    };
    return Pet;
}());
Enter fullscreen mode Exit fullscreen mode

Here we can notice our methods defined with different ways. The getName method still placed in prototype, but getNameArrow has defined as property inside of object and it will be created for every single instance. Let’s check it in real case:

const pet = new Pet();
const pet2 = new Pet();

console.log(pet.getNameArrow === pet2.getNameArrow); // false
Enter fullscreen mode Exit fullscreen mode

Every time when we create new instance, we create new function getNameArrow as well. Just imagine the case when we need 1000 instances of our Pet with 20 methods inside created as arrow function. It means will be created 20000 useless functions. And this is just when we use arrow functions for that.

This is actually root cause why we should avoid using arrow functions in classes, or at least think and understand when we can do it. If you know different cases or disagree with that, please put your thoughts to comments.

Oldest comments (3)

Collapse
 
sebbdk profile image
Sebastian Vargr

Kudos for pointing this out, this really matters when things start to scale. :)

Collapse
 
sakhnyuk profile image
Michael Sakhniuk

Thank you, Sebastian!

Collapse
 
yakauverameyeu profile image
Yakau Verameyeu

I agree, that we should avoid them due to performance questions, but nevertheless, the arrow functions are still a must for callbacks. I cannot remember how many times I have debugged the code like fetch(smth).then(this.processResponse); when the response handler was a method and certainly this reference got lost. It's possible to bind the instance method to a correct this reference or wrap the original one in the arrow wrapper, but in this case, we will produce new functions during the run.

So to my mind, this question has no straight response without doing the math in the exact use case you work on. You should compare the counts of how many times the binding will be called (if you use the method and then bind when needed) or duplicate functions will be created (if you instantiate an arrow function)