Warning: This blog post is outdated. Instead, please read the post “ ES proposal: public class fields ”.

The ECMAScript proposal “Class Fields” by Daniel Ehrenberg and Jeff Morrison is currently at stage 3. This blog post explains how it works.

Field declarations:

class MyClass { instanceProperty = 0 ; static staticProperty = 0 ; }

Private fields have names starting with a # and are only visible within the body of the class:

class MyClass { #foo; // must be declared constructor(foo) { this.#foo = foo; } incFoo() { this.#foo++; } }

Declaring fields #

With this proposal, objects now have two kinds of fields:

Properties, whose keys are strings or symbols.

Private fields that have names. More on private fields later.

Fields can be configured as follows:

Location of property: Static: prefix static Instance: no prefix

Visibility and name. A field can be either: A public property with a fixed name A public property with a computed key A private field with a fixed name

Initializer: optional

Location Visibility/name foo; instance public #foo; instance private ['f'+'oo']; instance computed static foo; static public static #foo; static private static ['f'+'oo']; static computed

With an initializer, you create a property and assign it a value at the same time. In the following code, = 0 is an initializer:

class MyClass { x = 0 ; y = 0 ; }

This class is equivalent to:

class MyClass { constructor () { this .x = 0 ; this .y = 0 ; } }

Initializers are executed before the constructor #

class MyClass { foo = console .log( 'initializer' ); constructor () { console .log( 'constructor' ); } } new MyClass();

Location of the field #

Instance fields #

Without any prefix, a declaration creates an instance field:

class MyClass { foo = 123 ; } console .log( new MyClass().foo); console .log( Reflect .ownKeys( new MyClass()));

Class fields #

Declarations with the prefix static create fields for classes:

class MyClass { static foo = 123 ; } console .log(MyClass.foo); console .log( Reflect .ownKeys(MyClass));

MyClass has the properties length and name , because it is also a function.

Private visibility #

Kinds of privacy in JavaScript #

In ES6 and later, you can already implement two kinds of privacy:

Soft privacy (that you can work around): put data into properties whose keys are symbols (details). const _counter = Symbol ( 'counter' ); const _action = Symbol ( 'action' ); class Countdown { constructor (counter, action) { this [_counter] = counter; this [_action] = action; } dec() { if ( this [_counter] < 1 ) return ; this [_counter]--; if ( this [_counter] === 0 ) { this [_action](); } } }

Hard privacy (that is mostly safe; for complete safety, you have to take additional precautions): data is in the values of WeakMaps whose keys are the objects that the private data is associated with (details). const _counter = new WeakMap (); const _action = new WeakMap (); class Countdown { constructor (counter, action) { _counter.set( this , counter); _action.set( this , action); } dec() { let counter = _counter.get( this ); if (counter < 1 ) return ; counter--; _counter.set( this , counter); if (counter === 0 ) { _action.get( this )(); } } }

Private fields are basically a more convenient way of doing hard privacy.

From underscores to private fields #

Another common technique is to indicate which properties are considered private by prefixing their names with underscores:

class Countdown { constructor (counter, action) { this ._counter = counter; this ._action = action; } dec() { if ( this ._counter < 1 ) return ; this ._counter--; if ( this ._counter === 0 ) { this ._action(); } } }

This technique doesn’t give you any protection, but it is more convenient than using symbols or WeakMaps.

Such code can be changed to use the new private field feature in two steps:

Replace each underscore with a hash symbol. Declare all private fields at the beginning of the class.

class Countdown { #counter; #action; constructor(counter, action) { this.#counter = counter; this.#action = action; } dec() { if (this.#counter < 1) return; this.#counter--; if (this.#counter === 0) { this.#action(); } } }

Countdown does not have any instance properties:

const countdown = new Countdown( 5 , () => {}); Reflect .ownKeys(countdown);

Private fields are similar to hard privacy via WeakMaps #

In the spec, private fields are managed via a data structure that is attached to objects. That is, private fields are roughly handled as follows.

{ const _counter = Symbol (); const _action = Symbol (); class Countdown { __PrivateFieldValues__ = { [_counter]: undefined , [_action]: undefined , }; constructor (counter, action) { this .__PrivateFieldValues__[_counter] = counter; this .__PrivateFieldValues__[_action] = action; } ··· } }

A consequence of this approach is that you can only access private fields if you are inside the body of a class; access to this does not give you access to private data. In other words, you need to know the right symbol to access the data ( __PrivateFieldValues__ in the example is not fully protected, but the corresponding data structure in the spec is).

More information in the spec: Sect. “Private Names and references”

Not yet supported #

Two elements of classes cannot yet be private:

Method definitions

Setters and getters

An upcoming proposal that fills this gap is currently at stage 2.

Trying out this proposal #

If you switch on stages 2+ in the Babel REPL, you can play with class fields (however, private fields are not supported, yet).

Why the # ? Why not declare private fields via private ? #

First, the # clearly indicates that private fields are not properties. They are a completely different mechanism.

Second, if you declared private fields via private and if they looked like normal properties, property lookup would become more complicated:

class MyClass { private name; compare(other) { return this .name === other.name; } }

If other is a direct(!) instance of MyClass , other.name is a private field. Otherwise, it refers to a normal property.

Further reading #