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:
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 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 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 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 = () => {
// ...
};
}
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!
}
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.
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.
#hashtag
?
We need to use this.#field
instead of this.field
for a couple reasons:
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.
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:
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!