Stage 0
Champion Needed
The following proposes a vision for private fields that conforms to the harmony of JavaScripts current syntax synergy.
Consider the following class.
class A {
constructor() {
this.id = Symbol('unique')
}
equal(instance, property) {
return this[property] == instance[property]
}
}
const x = new A()
x.equal(x, 'id')
Both active proposals that make use of the .# and -> sigils suffer from the same inability to implement this design pattern.
For example given the .# sigil, we might try to implement it in the following way, but the lack of computed access prevents us from achieving this goal.
class A {
constructor() {
this.#id = Symbol('unique')
}
get(instance, property) {
// how do I get the computed private properties of another instance of A?
return this.#id == instance.#id
}
}
const x = new A()
x.equal(x, 'id')
The same problem affects the -> sigil.
class A {
constructor() {
this->id = Symbol('unique')
}
get(instance, property) {
// same problem, how do I get the computed private properties of another instance of X?
return this->id == instance->id
}
}
const x = new A()
x.equal(x, 'id')
This proposal aims to achieve these requirements without introducing an asymmetrical new syntax for private fields.
The afor-mentioned affected pattern would be implemented in following:
class A {
private id = Symbol('unique')
equal(instance, property) {
return private(this)[property] == private(instance)[property]
}
}
const x = new A()
x.equal(x, 'id')
This introduces the use of the reserved keyword private
that acts symmetrical to the use of super
or import
where it is both a syntax i.e private[property]
and function private(this)[property]
.
This means that private(this)[property]
is equivalent to private[property]
.
The following examples demonstrates how this might look and work with other active/future proposals.
class A {
// uses the private keyword to create private fields, methods, getters etc.
private value = 'string'
private method () {}
// uses the static keyword to create static fields, methods, getters etc
static value = 'string'
static method () {}
// uses neiher to create instance fields, methods, getters etc
value = 'string'
method () {}
constructor() {
// invoke instance method
this['method'](this.value)
// use private.name or private['name'] to access private fields, methods, getters etc.
private['method'](private['value'])
// all of the following invocations are equivalent to the previous
private.method(private.value)
private(this)['method'](private(this)['value'])
private(this).method(private(this).value)
// assign private values
private.length = 1
private['length'] = 1
// future facing
static['method'](static['value'])
static.method(static.value)
}
}
What does private(this)[property] do?
"private(this)[property]" and alternatively "private[property]" or "private.property" all invoke access of a private "property" on the instance of the class, symmetrical to the syntax/function nature of both the "super" and "import" keywords.
What's private about private fields?
Outside of a private fields provider class, private fields/methods would not be accessible.
How do you prevent them from being forged or stuck onto unrelated objects?
Given the following:
class A {
private id = 0
private method(value) {
return value
}
write(value) {
private(this)["id"] = private["method"](value)
}
}
and then invoking the above write method with a this
value that is not an instance of A
, for example (new A()).write.call({}, 'pawned');
, would fail – the private syntax call site is scoped to the surrounding provider class. For example imagine the current possible transpilation of this using WeakMaps:
(function (){
var registry = WeakMap()
function A () {
registry.set(this, {id: 0})
}
A.prototype.write: function () {
registry.get(this)["id"] = registry.get(this.constructor)["method"].call(this, value)
}
// shared(i.e private methods)
registry.set(A, {
method: function (value) {
return value
}
})
return A
})()
Trying to do the the afore-mentioned forge here would currently fail along the lines of cannot read property id
of undefined
.
An instance has a fixed set of private fields which get created at object creation time.
The implications of this alternative do not limit the creation of private fields to creation time, for example writing to a private field in the constructor or at any arbitrary time within the lifecycle of the instance.
class HashTable {
constructor() {
private[Symbol.for('length')] = 0
}
set(key, value) {
private[key] = value
}
get(key) {
return private[key]
}
}
That would contradict your previous answer to the hijacking question. In the transpilation you created the field using "registry.set(this, {id: 0})", in the constructor. If you then claim that any write to the field can also create it, then you get the hijacking behavior which you wrote doesn't happen.
The difference between the following
class A {
private id = 0
}
and the following
class A {
constructor() {
private.id = 0
}
}
is similar to the difference between
(function (){
var registry = WeakMap()
function A () {
registry.set(this, {id: 0})
}
return A
})()
and
(function () {
var registry = WeakMap()
function A () {
registry.set(this, {})
registry.get(this)["id"] = 0
}
return A
})
This in no way permits the hijacking behavior previously mentioned – (new A()).write.call({}, 'pawned')
Do you limit classes to creating only the private fields declared in the class, or can they create arbitrarily named ones?
Just as you could write to arbitrary named fields with the mentioned WeakMap approach, you can also do the same for this alternative, for example –
private[key] = value
// or
private(this)[key] = value
What does static['method'] and static.method refer to?
This hints to a future facing proposal for static fields/methods that can share the same syntax symmetry of the proposed private fields/methods.
- Yet another approach to a more JS-like syntax
- Private and Protected are like Static and Super
- Proposal About Private Symbols
- Related Esdiscuss Thread
Syntax synergy is a strong contributing factor to the introduction of this alternative and a much stronger contributing factor in the community push back against a #sigil
direction to private fields/methods. In demonstration, the following is non-exhaustive collection documenting issues/disagreement/outlets expressed with the current #sigil
syntax.
Details
- Why not use the "private" keyword, like Java or C#?
- This proposal does not address the actually existing needs for private fields
- Yet another approach to a more JS-like syntax
- Proposal: keyword to replace
#
sigil - Please do not use "#"
- Private and Protected are like Static and Super
- New, more JS-like syntax
- Stop this proposal
- Why not use private keyword instead of #?
- Independent private field
- Syntax change suggestion
- Yes, the negative reaction of #priv is worse, I think it's the worst in our history. I'll try to explain it.
- Hash -> Underscore
- Can we just use private keyword?
- Is this really needed if we need a sigil
- Accessing private fields
- Use
private.x
syntax for referencing private fields - Unrelated but the syntax looks really weird.
- This is by far my least favourite proposal that has advanced through tc39.
- Why not use obj#prop instead obj.#prop
- Use this#prop instead this.#prop
- Not a fan of # for this, would prefer something with clearer semantics. # looks like bash comments or hashtags.
- Agreed - I would prefer the keyword
private
. - Python @ThePSF would be disappointed in JS. Definitely a feature I'd prefer not to have.
- Even if the feature is interesting, I am really not a fan of the syntax
- Why dont we use a keyword instead of a semantic character?
- If this is how I would have to write private methods, then I am not going to use them.
- So ugly and unnecessary
This proposal is a best affort to afford these issues an alternative syntax without sacrificing the current syntax synergy of the JavaScript language.