Craxal Craxal - 3 months ago 6
TypeScript Question

Function declarations or expressions for class methods?

A coworker of mine and I have had this discussion several times. There are two ways to define class methods. The first way is with a function declaration:

class Action {
public execute(): void {
this.doSomething();
}
...
}


Function declarations tend to be easier to read. Only one function object is used for each instance of
Action
, so they're also more memory-friendly.

The second is with a function expression:

class Action {
public execute: () => void = () => {
this.doSomething();
};
...
}


Function expressions need more typing (especially with type definitions), are harder to read, and generate a new function object for every instance of
Action
. And if you're generating lots and lots of objects, that's bad.

However, function expressions have one small benefit: they preserve the context of
this
(i.e. the
Action
instance) no matter who calls them:

var instance = new Action();
setTimeout(instance.execute);


A method declared as a function expression works as expected in this case. Function declarations fail miserably, but they can easily be fixed by doing this instead:

var instance = new Action();
setTimeout(() => instance.execute());


So, is one considered better practice over the other, or is this purely situational/preferential?

Answer

In my opinion arrow functions should be used as class methods only when you know for sure that the function might be called with a different context for this (if it's passed as an event handler for example) and you prefer to avoid using Function.prototype.bind.

There are several reasons for that, including as you wrote the code readability, but the main reason is of inheritance. If you use an arrow function then you simply assign a function to the instance as a member, but the function won't be added to the protptype:

// ts
class A {
    fn1() {}

    fn2 = () => {}
}

// js
var A = (function () {
    function A() {
        this.fn2 = function () { };
    }
    A.prototype.fn1 = function () { };
    return A;
}());

So what happens if you want to extend this class and overrride the fn2 method?
Because it's a property and not part of the prototype you'll need to do something like:

class B extends A {
    private oldFn2 = this.fn2;

    fn2 = () => {
        this.fn2();
    }
}

Which just looks terrible when compared to:

class A {
    fn1() {}

    fn2() {}
}

class B extends A {
    fn2() {
        super.fn2();
    }
}

Edit

There are a few reasons to prefer using the bind method over an anonymous function, I find it to be more implicit because it's the exact same function but bound to a specific this, on the other hand in the anonymous function you can add more code other than calling the actual function.

Another thing is that the bind function lets you not only bind which object will be considered as this, but also to bind parameters:

function fn(one, two, three) {}
fn.bind(null, 1, 2)(3);
fn(1, 2, 3);

The two invocations of fn here are the same.
You can do that with an anonymous functions, but not always:

var a = ["zero", "one", "two", "three", "four", "five"];
function fn(value, index) {
    console.log(value, index);
}

// works
a.forEach((item, index) => {
    setTimeout(() => {
        fn(item, index);
    }, 45);
});

// works
for (let i = 0; i < a.length; i++) {
    setTimeout(() => {
        fn(a[i], i);
    }, 45);
}

// doesn't work as i is undefined when the function is invoked
for (var i = 0; i < a.length; i++) {
    setTimeout(() => {
        fn(a[i], i);
    }, 45);
}

// works because the value of i and the value of a[i] are bound
for (var i = 0; i < a.length; i++) {
    setTimeout(fn.bind(null, a[i], i), 45);
}