jects 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 |
Initializers
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:
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.