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());
// or
setTimeout(instance.execute.bind(instance));
So, is one considered better practice over the other, or is this purely situational/preferential?
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, for code readability, but the main reason is for 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 prototype:
// 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 override 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();
}
}
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);
}