Arrow Functions in Class Properties Might Not Be As Great As We Think
Since the last year, the Class Properties Proposal simplify our life, especially in React with the internal
state , or even with statics ones like
Furthermore, Class Properties/Property Initializers seems to be more trending in the last six months to handle binding in React instead of a bind call in the constructor.
Arrow functions in class field properties seem useful because they’re autobind, in short, no need to add
this.handleClick = this.handleClick.bind(this) in the constructor.
But, should we really use arrow functions in class field properties?
First of all, let’s see what class properties do under the hood.
What Class Properties look like once transpiled to ES2017
Let’s code a simple class with a static property, an instance one, an arrow function in property, and a usual function as method with the plugin
Once we go to the Babel REPL to get this class transpiled to ES2017 with the following presets:
We got this transpiled version:
As we can see, instance properties have been moved inside the constructor and the static one moved to an afterward declaration.
Personally, I like the addition of static keyword as we can now directly
export a class with static properties.
On instance properties, it’s great too, because in our code (not transpiled one) the constructor will be less bloated with declarations.
For our arrow function in a property,
handleClick got moved in the constructor too, such as an instance property.
For our usual function method
handleLongClick, nothing change.
Property initializers may be useful for properties, but when it comes to arrow functions in class properties, it feel like a hackish way of achieving a binding.
Did you spot some issues? Let’s see what I found.
If you want to mock or spy on a class method, the easiest and proper way to do so is with the prototype as all changes to the
Object prototype object are seen by all objects through prototype chaining.
Let’s say we want to do some tests with our previous class
A.prototype.handleLongClick is defined.
A.prototype.handleClick is not a function.
Oops, since we used an arrow function in a class property our function
handleClick is only defined on the initialization by the constructor and not in the prototype. So, even if we mock our function in the instantiated object, the changes won’t be seen by other objects through prototype chaining.
Let’s define our base class
B inherit from class
handleClick won’t be in the prototype and we can’t call
super.handleClick from our arrow function
C inherit of class
A , but implement
handleClick as a function instead of an arrow function,
handleClick will only executes
super.handleClick() and nothing else. Strange isn’t?
It’s because the instantiation of
handleClick in the constructor of our parent class overrides it.
C.prototype.handleClick() will call our implementation but will fail with the previous error:
Uncaught TypeError: (intermediate value).handleClick is not a function .
D is a plain class that inherit from class
A , he will have an empty prototype and
new D().handleClick() will log
Now the interesting part, let’s talk about performance.
On the other hand, for the arrow functions in class properties, if we’re creating N components, these N components will also create N functions. Remember what we’ve seen in the transpiled version, class properties are initialized in the constructor. Which means if we click on N components, N different functions will be called.
Let’s see how they are doing in a benchmark with V8 engine (Chrome).
The first one is simple, we only measure the instantiation time, and we call our method one time.
Beware that number doesn’t really matter in this one, since one instantiation won’t be noticed in your application and we’re talking about operations per second and the number are high enough. I’m more concerned by the gap between our functions.
For the second one, I used a representative use case. The instantiation of 100 components — like a list — which after we called the method one time on each.
All benchmarks were run on a MacBook Pro 13" 2016 2GHz on Mac OS X 10.13.1 and Chrome 62.0.3202.
In short, to improve performance, you should declare your shared method in the prototype and only bound it to the context if you need to (if you pass it as prop or callback). It makes sense to bound our shared methods to the prototype and initialized our properties in the constructor of each instance, but methods not much.
We’re still talking about high ops/s, but we clearly see that arrow functions in class properties are not as performant as we thought.
And yes you’re right, the usage of property initializers won’t be noticed in our applications unless we instantiate many components. Yes we can see this as a premature optimization and you’re right premature optimizations are the root of all evil — with E Corp — but instead, can we see arrow functions in class properties as premature feature or misused feature? Hopefully, engines will optimizes the arrow functions in class properties.
P.S: Class properties for properties are such a great improvement!
I’ve seen this in many application and libraries, even ones that contain components we can instantiate multiple times. Should we really use this ESnext feature for class methods in our packages knowing that can impact — for the moment — our performance?
Personally, I don’t think that arrow functions in class properties are convenient enough — in some cases — at the expense of the performance.
Our savior will be the autobind-decorator, unfortunately it’s only available with babel as it’s still a proposal at stage 2.
Even this, they also recommend to avoid autobind on all methods or class:
It is unnecessary to do that to every function. This is just as bad as autobinding (on a class). You only need to bind functions that you pass around. e.g.
fetch.then(this.hanldeDone)— Dan Abramov
I was the guy who came up with autobinding in older Reacts and I’m glad to see it gone. It might save you a few keystrokes but it allocates functions that’ll never be called in 90% of cases and has noticeable performance degradation. Getting rid of autobinding is a good thing — Peter Hunt
The initialization of arrow functions in class properties are transpiled into the constructor.
Arrow functions in class properties won’t be in the prototype and we can’t call them with
Arrow functions in class properties are much slower than bound functions, and both are much slower than usual function.
You should only bind with .bind() or arrow function a method if you’re going to pass it around.
Knowing how arrow functions in class properties are handled under the hood and with these benchmarks, you should be able to make an informed choice and aware of the possible consequences.
What do you think? Will you still use it? How will this affect you?
Feel free to share your thoughts.