Demystifying Memory Usage using ES6 React Classes

Which is more efficient? Binding in the constructor, or using an arrow function as a class property?

Photo by Michal Lomza on Unsplash

There have been some good articles written on the various ways to write class methods using ES6. Most mention performance (i.e. speed of execution), but I have yet to see one dedicated to the memory impact.

More recently, the topic came up again — spurred on with a tweet by Axel Rauschmayer. Many people expressed their thoughts and ideas, but it was clear that many people are simply confused.

What this article is NOT

In this article, I’ll attempt to explain what I couldn’t really do in 140, um, 280 characters. I won’t talk about execution speed differences. I won’t talk about how passing a lambda function to a component breaks the shallow comparison of props. I won’t express an opinion on which method you should chose. I’ll only talk about the facts of memory usage and let you make an informed decision.

So with that, let’s look at two common solutions: using bind in the constructor, and using class property methods.

Constructor bind vs class properties

What do I mean when I say class property? Here’s an example.

class MyClass extends Component {
constructor() {
super();
this.state = { clicks: 0 };
}
  handler = () => {
this.setState(({ clicks }) => ({ clicks: clicks + 1 }));
}
  render() {
const { clicks } = this.state;
return(
<button onClick={this.handler}>
{`You've clicked me ${clicks} times`}
</button>
);
}
}

It uses a yet-to-be approved ES class property specification to set a function expression to an instance variable of a class.

And here’s how we might do the same thing using constructor bind. Notice that this syntax is less terse that in the example above and adds more “visual clutter”.

class MyClass extends Component {
constructor() {
super();
this.state = { clicks: 0 };
this.handler = this.handler.bind(this);
}
  handler() {
this.setState(({ clicks }) => ({ clicks: clicks + 1 }));
}
  render() {
const { clicks } = this.state;
return(
<button onClick={this.handler}>
{`You've clicked me ${clicks} times`}
</button>
);
}
}

Before we take a look at what’s going on in the constructor of the first example, let’s examine what is happening with an ES6 class method. Remember how you would have coded something like this way back in the day (i.e. just a few years ago) before ES6 classes? You might have done something like this.

MyClass.prototype.handler = function handler() {
...
};

This is exactly what ES6 sugars for you when you create a class method.

So what is the ES5 equivalent of what’s happening in the constructor?

function MyClass() {
this.handler = this.handler.bind(this);
}

Exactly what you might expect. But what is going on? If we look on MDN at Function.prototype.bind(), we will see that it the bind method:

creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called

So binding in the constructor will make it so when we call handler, the value of the instance property handler (which contains a pointer to the lambda function) is called, which sets this and calls the prototype function.

The only instance overhead, memory wise, is the function pointer to the native lambda function. The method itself stays on the prototype.

You can see both methods live on this CodeSandbox below.

Both methods act exactly the same, but what about their memory footprint?

Memory footprints

I’ve drawn these diagrams below to help explain what the memory footprint looks like for each solution. Note that the red area is the class — green areas are instances. Solid boxes show where memory is used. Dashed boxes are references to the inherited methods in the base class. These references utilize an insignificant amount of memory when compared to method in an instance.

First we’ll look at the class property method (i.e. the one that use an arrow function for handler).

Notice that the base MyClass holds only the render method. All other memory is consumed on a per-instance basis (the solid boxes). Each instance holds not only only state (as you might expect) and a reference to the base render, but also a handler method. If you have only a few instances, this is probably not a big deal.

Now let’s take a look at how memory looks when we bind in the constructor.

Here the base MyClass holds a render and a handler methods. This time each instance holds only state and a small lambda function that calls the hander method of the base class. A much smaller memory footprint per instance.

There is one caveat to all of this. The memory saving only applies if you are making multiple instances of a class. A single list view in your app, for example. But for classes that you instantiate multiple times (a list item for example), you will see a memory savings.

Conclusion

While the memory consumption is “lighter” than using the constructor bind, it’s certainly not as convenient. And if you have but a single instance (or two or three), the saving gained by manually binding may not be worth it.

In most cases it shouldn’t matter one way or another. Now that you know the facts, you can make your own decision based on your use case.

Frankly, I like the syntax of the class property. The best of both worlds, IMO, would be a Babel transform that converted a class property method to a prototype with a constructor bind automatically. If you know of one, or are ambitious enough to write one, let me know.

Remember: computers are really good at reading code—you shouldn’t worry about that. You may want to consider making your code more human readable by using a class property arrow function.

I also write for the American Express Engineering Blog. Check out my other works and the works of my talented co-workers at AmericanExpress.io. You can also follow me on Twitter.