JavaScript's new #private class fields

What they are, how they work, and why they are the way they are

This article is also available in Russian

Private class fields are now at Stage 2 in the JavaScript standard process. It's not finalized yet, but the JavaScript standards committee expects the feature to be developed and eventually included in the standard (although it may still change).

The syntax (currently) looks like this:

class Point {
  #x;
  #y;

  constructor(x, y) {
    this.#x = x;
    this.#y = y;
  }

  equals(point) {
    return this.#x === point.#x && this.#y === point.#y;
  }
}

There are two main parts of this syntax:

  1. Defining private fields
  2. Referencing private fields

Defining Private Fields

Defining private fields is mostly the same as defining public fields:

class Foo {
  publicFieldName = 1;
  #privateFieldName = 2;
}

In order to access a private field, you need to have defined it. So in case you don't want to instantiate a value when defining the property, you can do so:

class Foo {
  #privateFieldName;
}

Referencing Private Fields

Referencing private fields works similar to accessing any other property, only it has a special syntax.

class Foo {
  publicFieldName = 1;
  #privateFieldName = 2;
  add() {
    return this.publicFieldName + this.#privateFieldName;
  }
}

There's also a shorthand version of this.#:

method() {
  #privateFieldName;
}

Which is the same as:

method() {
  this.#privateFieldName;
}

Referencing private fields of instances

Referencing private fields isn't just limited to this. You can also access private fields on values that are instances of your class:

class Foo {
  #privateValue = 42;
  static getPrivateValue(foo) {
    return foo.#privateValue;
  }
}

Foo.getPrivateValue(new Foo()); // >> 42

Here, foo is an instance of Foo so we are allowed to lookup #privateValue from within the class definition.

Private Methods (coming soon?)

Private fields are coming as part of a proposal focusing on just adding class fields. The proposal does not make any changes to class methods, so private class methods will be coming in a followup proposal and will likely look like this:

class Foo {
  constructor() {
    this.#method();
  }
  #method() {
    // ...
  }
}

In the meantime, you can still assign functions to private fields:

class Foo {
  constructor() {
    this.#method();
  }

  #method = () => {
    // ...
  };
}

Encapsulation

If you're using an instance of a class, you cannot reference that class's private fields. You can only reference private fields from within the class that defines them.

class Foo {
  #bar;
  method() {
    this.#bar; // Works
  }
}
let foo = new Foo();
foo.#bar; // Invalid!

Further, to be truly private, you shouldn't be able to even detect that a private field exists.

In order to make sure that you can't detect a private field, we need to allow public fields with the same name.

class Foo {
  bar = 1; // public bar
  #bar = 2; // private bar
}

If private fields didn't allow for public fields with the same name, you could detect the private fields existence by trying to write to a property of the same name:

foo.bar = 1; // Error: `bar` is private! (boom... detected)

Or the silent version:

foo.bar = 1;
foo.bar; // `undefined` (boom... detected again)

This encapsulation should also be true for subclasses. A subclass should be able to have a field of the same name without having to worry about the parent class.

class Foo {
  #fieldName = 1;
}

class Bar extends Foo {
  fieldName = 2; // Works!
}

So why the hashtag?

A lot of people are wondering "why not follow conventions from many other languages and use a private keyword"?

Here's an example of that syntax:

class Foo {
  private value;

  equals(foo) {
    return this.value === foo.value;
  }
}

Let's look the two parts of the syntax separately.

Why don't declarations use the private keyword?

The private keyword is used in a lot of different languages to declare private fields.

Let's look at the syntax of language like that:

class EnterpriseFoo {
  public bar;
  private baz;
  method() {
    this.bar;
    this.baz;
  }
}

In these languages, public and private fields are accessed the same way. So it makes sense that they get defined this way.

However, in JavaScript, because we can't use this.field for private properties (which I'll get to in a second), we need a way of syntactically communicating the relationship. By using the # in both places, it's much clearer what is being referenced.

Why do references need the #hashtag?

We need to use this.#field instead of this.field for a couple reasons:

  1. Because of #encapsulation (see the "Encapsulation" section above), we need to allow public and private fields with the same name at the same time. So accessing a private field can't just be a normal lookup.
  2. Public fields in JavaScript can be referenced via this.field or this['field']. Private fields won't be able to support the second syntax (because it needs to be static) and that could lead to confusion.
  3. You'd need expensive checks:

Let's take a look at a code example.

class Point {
  #x;
  #y;

  constructor(x, y) {
    this.#x = x;
    this.#y = y;
  }
  equals(other) {
    return this.#x === other.#x && this.#y === other.#y;
  }
}

Notice how we're referencing other.#x and other.#y. By accessing the private fields, we are assuming that other is an instanceof our class Point.

Because we've used the #hashtag syntax we've told the JavaScript compiler that we're looking up private properties from the current class.

But what would happen if we didn't use the #hashtag?

equals(otherPoint) {
  return this.x === otherPoint.x && this.y === otherPoint.y;
}

Now we have a problem: How do we know what otherPoint is?

JavaScript doesn't have a static type system, so otherPoint could be anything.

Which is a problem for two reasons:

  1. Our function behaves differently depending on what type of value you pass to it: Sometimes accessing a private property, other times looking up a public property.
  2. We'd have to check the type of otherPoint every single time:
if (
  otherPoint instanceof Point &&
  isNotSubClass(otherPoint, Point)
) {
  return getPrivate(otherPoint, 'foo');
} else {
  return otherPoint.foo;
}

Even worse, we'd have to do this for every single property access within a class to check if we're referencing a private property.

Property access is already really slow, so we definitely do not want to add any more weight to it.

TL;DR: We need to use a #hashtag for private properties because the alternative of using standard property accesses would create unexpected behavior and result in huge performance problems.


Private fields are an awesome addition to the language. Thanks to all the wonderful hardworking people on TC39 who made/are making them happen!