03.02.2022 - TypeScript/Arrow Function in Classes

All posts

Posted On 03.02.2022

One of the most confusing things in JavaScript is the context object this inside a function. The value of this depends on how the function is called, not how the function is defined.

Let’s take a look at this example:

class Foo {
    name = "foo";
 
    sayHello() {
        console.log(this.name);
    }
}
 
let a = new Foo();
a.sayHello();

The output of the above snippet is as expected:

"foo"

But if we change the code to this:

let a = new Foo();
const wrapper = {
    name: "awesome",
    fn: a.sayHello
};
wrapper.fn();

Now, the output is:

"awesome"

It’s because the context of the sayHello function has been changed when we wrap it inside the wrapper object.

One way to fix this is to re-bind the method in the class’s constructor:

class Foo {
    ...
 
    constructor() {
        this.sayHello = this.sayHello.bind(this);
    }
 
    ...
}
 
let a = new Foo();
const wrapper = {
    name: "awesome",
    fn: a.sayHello
};
wrapper.fn();

The output is now correctly displayed:

"foo"

Another way to solve this context problem, and avoid re-bind the methods in the class’s constructor is to use the arrow function:

class Foo {
    name = "foo";
 
    sayHello = () => {
        console.log(this.name);
    }
}

This will preserve the this context inside the sayHello method, regardless of how the method is called.

But there is a memory and performance impact when doing this. The Handbook only mentioned it in a single line without any further explanation:

This will use more memory, because each class instance will have its own copy of each function defined this way

I was confused about this statement, so I decided to take a closer look to find out why.


Let’s take a look at another example:

class Foo {
    hello() {
        return "Hello";
    }
 
    foo = () => {
        return "Hello";
    }
}

Compile this snippet to JavaScript, this is what we get:

"use strict";
class Foo {
    constructor() {
        this.foo = () => {
            return "Hello";
        }    
    }
 
    hello() {
        return "Hello";
    }
}

Now it makes more sense. The foo arrow function becomes a property of the Foo class and gets defined inside the class’s constructor. Because the syntax that we are using to define the foo function is actually class’s property definition syntax:

class A {
    prop = <value>;
}

If you inspect the prototype of the Foo class, what you see is:

Class Foo
    ┗ Prototype
        ┣ constructor: function() { ... }
        ┗ hello: function() { ... }

Every instance of the Foo class will get its own version of the foo method, which will be created at runtime, inside the constructor. And it will take a lot more memory.

There is a performance impact when defining a class’s method as an arrow function like this as well.

As we already know, most JavaScript engines do a very good job to optimize code execution in various ways. One of these techniques is to detect if a function is being called over and over again (a hot function). With the class’s function, the engine easily knows that it’s the same function in the class prototype and it can do the optimization if needed. But with the case of the arrow function, we are technically calling different functions from different instances, so, the JS engine won’t be able to do any optimization.

You can check the benchmark between the two cases in Nicolas Charpentier’s article: Arrow Functions in Class Properties Might Not Be As Great As We Think, but be mindful that the article was written from 2017, and the way browser engines works might change a lot since then.