-
Notifications
You must be signed in to change notification settings - Fork 113
Derived class access #12
Comments
There isn't really much interaction. You can't even attempt to access a private field outside of the class in which it's defined - it is a SyntaxError to try. So: class Base {
#f;
}
class Derived extends Base {
m() {
this.#f; // SyntaxError: #f is not defined
}
} As in other languages, you can have a private field in a derived class of the same name as a private field in a base class, and it will "just work": class Base {
#f = 0;
baseMethod() {
console.log(this.#f);
}
}
class Derived extends Base {
#f = 1;
derivedMethod() {
console.log(this.#f);
}
}
const instance = new Derived();
instance.baseMethod(); // 0
instance.derivedMethod(); // 1 There is not, in this proposal, any first-class notion of "protected", or of "readonly". You can use Symbols for something kind of like protected fields: const p = Symbol();
class Base {
[p] = 0;
}
class Derived extends Base {
m() {
console.log(this[p]); // 0
}
} though of course these are accessible to other code via For "readonly" fields, you may be able to use a decorator, or you can use a getter: "use strict";
class A {
get v() {
return 1;
}
}
(new A()).v = 2; // TypeError: cannot set property 'v' of [object Object] |
Having worked in some very large codebases these features are something I'm keen on. I don't really care about access through getOwnPropertySymbols or iterating over private/public fields via some sort of reflection. What I do care about is how developers signal intent to each other. The use of the symbols could certainly make protected fields a possibility, but it would be nice to have some syntactic sugar for it. 'readonly' is an interesting one. Yes we can do it with getters. In the C# sense readonly means that the value can only be set in the constructor or field initialiser. This is a great way to signal that this value is meant to be immutable. I'm not sure if the following is possible, but I guess this is how we'd do readonly using decorators. class A {
@readonly v;
constructor(n) {
this.v = n;
}
} becomes class A {
#v;
get v() {
return #v;
}
constructor(n) {
this.#v = n;
}
} |
I did try to come up with an example of how protected could work, but I don't really like it. Also the use of this[a] is really not intuitive. // approach 1 - using an internal class with getters and setters to make the experience a little better
const a = Symbol();
// exported outside the library
class Base {
[a] = 0;
addVal(val) {
this[a] = this[a] + val;
}
addVal2(val) {
this[a] += val;
}
}
// internal - not exported outside the library
class InternalBase {
get a() { return this[a]; }
set a(val) { this[a] = val; }
}
class Derived extends InternalBase {
m(b) {
this.a = this.a * b;
}
} |
We discussed whether private fields should be accessible outside the class that way at length in tc39/proposal-private-fields#33 . The conclusion was to maintain a strong privacy boundary. |
Yeah, I tried reading that thread originally and had to stop when it spiraled out of control. Just did my third pass of it. Was there further/offline discussion of @wycats proposal around adding a "secret" property for decorators? (tc39/proposal-private-fields#33 (comment)) If the decorators supported the "secret" property then you could implement private/protected members using symbols and decorators. Would it not be simpler than adding a private field slot? And if you didn't want to add the 'secret' option then we just need to make the symbol not iterable, and we could achieve the same outcome. |
There was some further offline discussion. My impression of the end result is that both cases seem to have some use, but there's definitely a lot of use for the secret case, and we ended up taking the side of secret as the default. Decorators' integration with private fields (and methods) to allow a non-secret decorator is a major goal of decorators/private/fields integration. Maybe @wycats could say more. |
I see the addition of a 'secret' or 'hidden' property to the decorators negating the need for private fields. Something like: function private(target, key, descriptor) {
descriptor.secret = true;
descriptor.enumerable = false;
}
const field = new Symbol()
class Foo {
@private [field] = "123";
} Then a |
@sugendran It's unclear to me how this would work. Could you elaborate? |
My understanding of the private fields proposal is that we want private fields but we need to satisfy that:
1 and 2 can be satisfied if the property is a Symbol. A derived class would only be able to access the property using the Symbol, so it has be a deliberate choice on the developer to allow that to happen. 3 can be satisfied if we say that setting 'secret' to true on the descriptor prevents it from being listed in getOwnPropertySymbols and getOwnPropertyNames |
"secret" symbols were rejected in the ES6 cycle, since it was unclear how to explain why they don't hit Proxy traps (and, if they don't hit Proxy traps, what are proxies really good for?). This proposal uses private names instead, which are not properties at all. I'm closing this issue for now; feel free to reopen if there's a new idea for how to square that circle. |
Firstly, I don't want to rehash the "private" keyword
argumentdiscussion as that is happening elsewhere.One thing that is not clear to me is the interaction between a class and the parent class that it is derived from. Could we get some examples of how private/public fields work (or not work) in the scenarios below?
The text was updated successfully, but these errors were encountered: