Deep dive into public and private instance fields in JavaScript (ECMAScript 2022)


The class fields proposal expands the existing JavaScript class syntax with new functionality. This new functionality has been promoted to Stage 4 in the TC39 process and will be officially included in ECMAScript 2022.

In this blog, we will look into public and private instance class fields in detail.

Public instance fields

Public class fields allow us to add instance properties to the class definition with the assignment operator (=).

Before

Consider a simple React component of incrementing the count.

import React, { Component } from "react";

export class Incrementor extends Component {
  constructor() {
    super();
    this.state = {
      count: 0,
    };
    this.increment = this.increment.bind(this);
  }

  increment() {
    this.setState({ count: this.state.count + 1 });
  }

  render() {
    return (
      <button onClick={this.increment}>Increment: {this.state.count}</button>
    );
  }
}

In this example, we define instance fields and bind methods in the constructor. We can make the code a little more intuitive by using the new class syntax.

After

The new public class fields syntax allows us to add instance properties directly as a property on the class without having to use the constructor method. This simplifies the class definition making the code look pretty readable with less boilerplate!

import React from "react";

export class Incrementor extends React.Component {
  state = { count: 0 };

  increment = () => this.setState({ count: this.state.count + 1 });

  render = () => (
    <button onClick={this.increment}>Increment: {this.state.count}</button>
  );
}

Now, a lot of people reading this are probably thinking “Well, we’ve been able to do this in JavaScript for ages”, which is true. But, it is not standard ECMAScript and isn’t enabled by default. If the project is created using create-react-app, it is enabled by default or else we have to include the right Babel plugin. However, Babel 7.14 enables class fields & private methods by default in @babel/preset-env.

Let’s check out more about public instance fields.

  • Public instance fields exist on every created instance of a class. They are added with Object.defineProperty() either at construction time in the base class (before the constructor body runs) or just after super() returns in a subclass.
class Incrementor {
  count = 0
}

const instance = new Incrementor();
console.log(instance.count);
// expected output: 0
  • Fields, which are not initialized are set to undefined.
class Incrementor {
  count
}

const instance = new Incrementor();
console.assert(instance.hasOwnProperty('count'));
console.log(instance.count);
// expected output: "undefined"
  • Like properties, field names may be computed.
const PREFIX = 'main';

class Incrementor {
  [`${PREFIX}Count`] = 0
}

const instance = new Incrementor();
console.log(instance.mainCount);
// expected output: 0

Private instance fields

Private fields are accessible on the class constructor from inside the class declaration itself. When we want to prevent people from accessing the property directly private class fields should be used.

Private fields are based on syntax using a #, both when declaring a field and when accessing it.

class Incrementor {
  #count = 0;
  fetchCount() {
    console.log(this.#count);
  }
}

const instance = new Incrementor();
console.log(instance.fetchCount()); // 0
instance.#count === 42;   // Syntax error

Check out more details about the private instance fields in our previous blog.

See the private syntax FAQ for discussion of alternatives considered and the constraints that led to this syntax.