The insights shared in this React response are still applicable across various frameworks, including Angular and vanilla JavaScript/TypeScript.
While ES6 class prototype methods are commonly used, it's worth noting that class arrow methods are not part of the existing specs but rather belong to the class fields proposal. They can be implemented in TypeScript and transpiled with Babel.
In general, utilizing prototype method() { ... }
is preferred over arrow method = () => { ... }
for its flexibility.
Callbacks
The main advantage of arrow methods is their seamless use as callbacks:
class Class {
method = () => { ... }
}
registerCallback(new Class().method);
If a prototype method needs to be used as a callback, it should be explicitly bound, preferably in the constructor:
class Class {
constructor() {
this.method = this.method.bind(this);
}
method() { ... }
}
registerCallback(new Class().method);
In TypeScript and ES Next, a decorator like bind-decorator
can offer a more concise alternative to method binding in the constructor:
import bind from 'bind-decorator';
class Class {
@bind
method() { ... }
}
Inheritance
Using arrow methods in parent classes restricts child classes to also utilize arrow methods to override them. This limitation can lead to issues if arrow methods are overlooked:
class Parent {
method = () => { ... }
}
class Child extends Parent {
method() { ... } // won't override parent method
}
Due to this restriction, using super.method()
in the child class becomes problematic since super.method
refers to Parent.prototype.method
which does not exist:
class Parent {
method = () => { ... }
}
class Child extends Parent {
method = () => {
super.method(); // won't work
...
}
}
Mixins
Prototype methods lend themselves well to mixins, offering efficient solutions for multiple inheritance or addressing TypeScript method visibility challenges:
As arrow methods are not accessible on the class prototype, they cannot be extended beyond the class scope:
class Parent {
method = () => { ... }
}
class Child extends OtherParent { ... }
Object.assign(Child.prototype, Parent.prototype) // method won't be copied
Testing
A key advantage of prototype methods is their accessibility prior to class instantiation, enabling easy spying or mocking in tests even when called immediately after construction:
class Class {
constructor(arg) {
this.init(arg);
}
init(arg) { ... }
}
spyOn(Class.prototype, 'init').and.callThrough();
const object = new Class(1);
expect(object.init).toHaveBeenCalledWith(1);
This pre-instantiation access is not possible with arrow methods.
TL;DR: While the choice between prototype and arrow class methods may seem subjective, opting for prototype methods usually proves more prudent in the long run. Arrow class methods should be used judiciously, with caution about potential inconveniences. If passing prototype methods as callbacks, ensure to use bind
.