Make # an object on the instance #75
Description
Update at bottom of description
After reading through the FAQ and issues on the various related repos regarding the sigil I just wanted to run something else by the champions of this proposal.
Please forgive me if this has already been discussed but as I said there are various repos and lots of threads so I may have missed it.
I'm wondering if just making #
be a an object property of this
makes this proposal more consistent with existing paradigms in Javascript.
For example:
class {
private x = 'x';
method() {
console.log(this.#.x);
}
}
Would be sugar for:
(function(){
const __private__ = new Weakmap();
return class {
constructor(){
__private__.set(this, {
x: 'x'
});
}
method() {
console.log(__private__.get(this).x);
}
}
})();
Why?
Well I get the need for the #
sigil for assesor efficiency but there are a couple of points of this proposal that I find unnessarily restricitve.
- No variable accessors (e.g.
this['#x']
orthis[somevar]
). This would seem like any use case where you would need this for public properties could also be applied to private properties. - No private property iteration. To me there shouldn't be any reason I shouldn't be able to iterate over private properties without breaking encapsulation. I can think of one use case in particular for a ECS game engine I am working on.
- No destructuring. To avoid having to type
this.#
repeatedly it would be nice to be able to use destructuring at the beginning of a method to access private properties.
With this adjustment all of these should be possible without losing any encapsulation.
Variable accessors:
class {
private x = 'x';
private y = 'y';
method() {
const prop = condition ? 'x' : 'y';
this.#[prop] = somecomplexcalc(this.#[prop]) ;
}
}
vs
class {
#x = 'x';
#y = 'y';
method() {
if(condition) {
this.#x = somecomplexcalc(this.#x);
} else {
this.#y = somecomplexcalc(this.#y);
}
}
}
Private property iteration:
class {
private x = 'x';
private y = 'y';
private z = 'z';
method() {
for(const [prop, value] of Object.entries(this.#)){
this.#[prop] = somecomplexcalc(value);
}
}
}
vs
class {
#x = 'x';
#y = 'y';
#z = 'z';
method() {
this.#x = somecomplexcalc(this.#x);
this.#y = somecomplexcalc(this.#y);
this.#z = somecomplexcalc(this.#z);
}
}
Destructuring:
class {
private x = 'x';
private y = 'y';
private z = 'z';
method() {
const { x, y, z } = this.#;
}
}
vs
class {
#x = 'x';
#y = 'y';
#z = 'z';
method() {
const x = this.#x;
const y = this.#y;
const z = this.#z;
}
}
Why private in declaration? (I know it's been talked about but I have to try)
First consistency and readability:
class {
#x = 'x';
@protected #y = 'y';
static method (){}
}
vs
class {
private x = 'x';
protected y = 'y';
static method() {};
}
Secondly, with #
being a property of this
instead of part of the name of the property it makes more sense here.
Conclusion
To me, this makes this proposal seem a little less strange compared to the rest of Javascript. It's a simple mental model. All private properties are stored on this.#
which is only accessible from within the class methods.
Keeping the sigil for accessors means it's non breaking for any existing code so I'm ok with it there but I really hope we reconsider using private
in the declaration just to keep the syntax consistent and readable.
Edited: typo and broken formatting in github
Updated proposal
I wanted to provide an update to my proposal based on the discussions in the thread. I believe that the #
should be moved to before this
to make it clearer to developers that #this
is a lexically scoped variable.
class {
private x = 'x';
method(obj) {
console.log(#this.x, #obj.x);
}
}
Would be sugar for:
(function(){
const __private__ = new Weakmap();
return class {
constructor(){
__private__.set(this, {
x: 'x'
});
}
method(obj) {
console.log(__private__.get(this).x, __private__.get(obj).x);
}
}
})();
I have also taken the comments of @ljharb and @bakkot into consideration and I have a further addition that will prevent any leakage to mutations.
To avoid any leakage we can make it a syntax error to assign #this
to any variable, property, return value, or function parameter. This is actually a really simple syntax check. #
must be immediately followed by variable identifier which must be immediately followed by a .
or [
. With the only exception being for destructuring.
Syntax error example:
const x = #this;
const x = { #this };
const x = { x: #this };
const x = [#this];
somefunction(#this);
return #this;
I'm sure there tons I have missed here but as long as they follow those rules there shouldn't be any leakage.
Valid syntax examples:
const x = #this.x;
#this.method();
#this.x++;
#this['x']++;
const { x } = #this; // exception to the rule but doesn't cause leakage
const x = { ...#this }; //exception to the rule but doesn't cause leakage
This prevents any leakage for mutations.
This doesn't fully address the issue with private keyword implying this.x access but I think it comes pretty close.
Edit: I accidentally left out or [
syntax check that I had in my notes here