The proposal as of 9/17 places both instance property declarations and static property declarations inside the class body. For static properties, this is exactly right, as static properties are per-class and are initialized once when the class is initialized.
For instance properties, putting the declaration in the class body is exactly wrong, because instance properties are per-instance precisely in order to vary from one instance to another. The expression used to initialize an instance need to be in the scope of the constructor, as the initial value will often be calculated based on constructor parameters.
The most common response to this objection is that the constructor can re-initialize these properties by assignment. But this confuses initialization with mutation. A declarative property declaration syntax needs to be able to grow into the ability to declare const properties, i.e., non-writable, non-configurable data properties. Whether the ability to declare const properties is provided by syntax or by declaration, their unassignable nature prevents this re-initialization in the constructor.
To make this repair to the proposal, we of course need an alternate concrete syntax proposal. Here are some choices:
Overload the variable declaration syntax
The common ES6 way to give an instance properties is by assignment in the constructor body, such as:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
The first proposal would simply turn these assignments into declarative property initializations by prefixing them with let
or const
:
class Point {
constructor(x, y) {
const this.x = x;
const this.y = y;
}
}
This syntax is unambiguous, as it is currently illegal under the ES6 grammar.
Using the const
keyword declares the property to be a non-writable, non-configurable data property. Using the let
keyword makes a writable, non-configurable data property. Conceivably, using the var
keyword would make a writable configurable data property, although there's not much point since assignment already does this. In all cases, these declarations could be decorated.
In a base class as above, perhaps all these declarations must come textually before the first non-declaration use of this
, in order to prevent the instance from being observed before it is initialized. In a derived class constructor, perhaps all these declarations must come before the super
call, while this
is in a temporal dead zone, for the same reason. However, either of these requirements impedes refactoring ES6 code to turn instance assignment into initialization.
Use the reserved "public" keyword
This proposal is like the previous one, but using (and using up) the reserved "public" keyword rather than overloading the meaning of let
and const
:
class Point {
constructor(x, y) {
public this.x = x;
public this.y = y;
}
}
or perhaps
class Point {
constructor(x, y) {
public x = x;
public y = y;
}
}
By default, this makes writable non-configurable data properties. To declare a non-writable property instead, you'd use a decorator.
Since public
, unlike let
and const
, is not already associated with a TDZ concept, the initialization-time considerations above do not have as much force. Nevertheless, observing an uninitialized instance is a hazard that these restrictions can help programmers avoid.
Another possible advantage of public
is that private fields might then be declared with the same syntax but using the keyword private
.
Use C++-like property initialization syntax
class Point {
constructor(x, y)
:x(x), y(y) {
}
}
Personally I find it ugly. With decorators, they'd be even uglier. I list it because, given precedent, it should at least be mentioned as a candidate.