In the JavaScript class
syntax introduced in ES6 (static properties are accessible from the class, instance properties are accessible from the instances):
- static fields belong to the class;
- static methods belong to the class;
- instance methods belong to the instances’ prototype;
this
properties belong to the instances.
So I expected that instance fields would belong to the instances’ prototype, like instance methods. But that’s not the case, they get a special treatment and belong to the instances instead, like this
properties.
For example, the class
class A {
static x; // static field
static f() {} // static method
y; // instance field (special treatment)
g() {} // instance method
constructor() {this.z = 0} // this property
}
gives the results
> Object.getOwnPropertyNames(A)
[ 'length', 'name', 'prototype', 'f', 'x' ]
> Object.getOwnPropertyNames(A.prototype)
[ 'constructor', 'g' ]
> Object.getOwnPropertyNames(new A)
[ 'y', 'z' ]
Why do instance fields belong to the instances instead of their prototype?
A drawback that I see with this special treatment is that it prevents one from simply declaring non-function properties in the instances’ prototype, forcing one to do it after class declaration (or resort to a static initialization block):
> A.prototype.y2 = 0;
0
> Object.getOwnPropertyNames(A.prototype)
[ 'constructor', 'g', 'y2' ]
Another drawback is that if one wants to declare properties on the instances, there is already this
for that, so it duplicates an existing language feature.
2
Answers
class
syntax was just a syntactic sugar for existsing things:prototype
– an object with things shared between instances, mostly methods and get-set descriptorsconstructor
– a function to make objects with predefined prototypeconstructor
as a namespace to keep related thingsclass
syntax included all that… with exception of constants on prototype – they have very weird behaviour (actually being property initializers) and noone really ever used themPrior to the addition of public instance fields, those properties had always belonged to the instances (except in some rather obscure usage). And after the addition, they kept storing those properties in the instances.
In section 2.1 of this article by Dr. Axel Rauschmayer, it shows the motivation for adding public instance fields.
So, the addition of public instances can simplify constructors. It also makes it clear that the initial value of a property does not depend on the arguments of the constructor. (It may also help readability and make it easier for documentation)
There is also a semantic difference, as class fields use the
[[DefineProperty]]
semantics (with a behavior that matchesObject.defineProperty
), so with public fields, the setter in a superclass will not be called.Compare:
with
As for why those properties belonged to the instances rather than prototypes (also applicable in the days before
class
es became a thing in JS):If you have a property in the prototype, once you assign, say,
instance1.property1 = 3
, the value from the prototype is no longer used (property shadowing); a new property is created in the instance and the value in the prototype is not changed. So putting a value in the prototype is not very useful.In contrast, a method is usually not re-assigned, and it’s useful to share between instances.
instance1.method1(...)
andinstance2.method1(...)
call the same function with differentthis
. (So those are not aliases of each other as they have different effects).