-
Notifications
You must be signed in to change notification settings - Fork 113
Make # an object on the instance #75
Comments
What would the [[Prototype]] be for this object? Would i be able to pass it around and expose private values to mutation later, perhaps by accident as the receiver with |
See this recent thread on computed property access and iteration. Re: the syntax for declaration, I am strongly opposed to any proposal which has There's some other issues with this suggestion which I can go into detail about if you'd like, but these are some of the major points. |
Sure just as you could break encapsulation now by accidentally passing any enclosed object around. Accidentally passing
I've read that thread, I'm not sure I understand completely the opposition in the context of my suggestion. As you can see from my examples in the description, there are very common use cases for being able to reference a private property from a variable. If this is prohibited then it's going to lead to a lot of ugly
I've read the FAQ but it doesn't really give a convincing argument. The same could be said accidentally using |
@ljharb Not sure why you would but if you want to prevent getters and setters from being added later you can use (function(){
const __private__ = new Weakmap();
return class {
constructor(){
__private__.set(this, Object.seal({
x: 'x'
}));
}
}
})(); |
The suggestion is that you should use a private property with a Map if you for some reason need a private map, not that you should always do this. I would prefer to encourage the mental model that private properties are record-like.
I do not share your intuition that these are very common uses cases for private fields.
I don't share this intuition either - I don't think putting an object on a private field is all that ugly, and it seems to me it would satisfy your use cases without particular redundancy. In fact, if you had
I'm sorry you don't find the argument convincing. I do. I am less concerned (not unconcerned, but less) about Also, we do care what happens in other languages and would prefer to avoid misleading people with a background elsewhere, especially in cases where there is very similar syntax which has significantly different semantics in another language. We don't necessarily want to replicate other languages identically (and often are not in a position to in any case), but that doesn't mean we feel free to totally ignore them. |
If we want a record-like syntax than it shouldn't be accessed via
Having to do class {
x = 'x';
#y = 'y';
#_ = {
method () {
//how do I even reference x and y?
}
}
} vs class {
x = 'x';
private y = 'y';
private method () {
return this.x + this.#.y;
}
} Being sugar for something like: (function(){
const __private__ = new Weakmap();
const __methods__ = {
method: function() {
return this.x + __private__.get(this).y;
}
}
return class {
constructor(){
__private__.set(this, {
y: 'y',
method: __methods__.method.bind(this)
});
}
}
})(); |
@bakkot it seems your biggest complaints against this proposal are this:
I personally think that (with some small tweaks) this proposes a much less complicated, easier to understand implementation of privates, without really deviating too far from what's already in the main proposal. This would essentially be the same as the current implementation, except with only one private member, named simply To address concern # 1, the following would provide consistent declaration and access: class Foo {
x = "I'm pubic"
# = {
y: "I'm private",
method = () => this.#.x + this.y;
}
} This to me seems much easier for the uninitiated to comprehend. Rather than explain "Oh yeah, well a member prefixed by # means that the member is private. Except that really, it's not a member at all anymore. You can write a new member at any point, but privates all have to be declared up front in the class body. Also the name isn't With this, the explanation is a lot simpler. " (This has another nice parallel to how react state is usually declared and used, just to put that out there.. For concern # 2, I don't know that this is any more potentially dangerous than what is already possible. Sure, you could accidentally leak some information, but any potential for leak would also be present in the current implementation, via As for # 3, I really don't think correct to say that privates should only be record-like, or even to encourage users to only think of privates as record-like. Sure, the use cases for dynamic access and iterability are not the most common cases, but they certainly aren't non-existent. I don't see any value to treating private values as record-only. To the contrary; I think that having them be so, when the reasons why they are are so technical and non-obvious, creates a very strange limitation that breaks the user's intuition about how the language should work. When a user asks "why can't I iterate over private properties?" or "why can't I access this dynamically?" the discussion that will follow will be lengthy, and a tad unconvincing. The sheer number issues (and comments) coming up in this repo to the tune of "I read all the FAQ, and much of the conversation, but I still think it would be better to do ___" should be proof enough of that. Despite there being logical explanations for the choices made thus far, the community at large finds the reasoning dissatisfying. There's a general air of "I get it, but that doesn't mean I have to like it." As such, I feel like it's much easier to swallow having only one private member (an object named I apologize if any of this comes off as combative or stubborn. I don't really have any major technical arguments against the proposal as-is, other than the aforementioned feeling of "I get it, but that doesn't mean I have to like it". For better or for worse, whatever design is eventually accepted into javascript, we will be stuck with forever. I just want to make sure that we truly exhaust all our options before settling on a design. It would be a shame if a feature as highly desired as private data finally arrived, but was generally found to be unpleasant to work with, and confusing to new users. Before committing to any one design, we need to try to be absolutely sure that there truly is no better option. |
@EthanRutherford I'm ok with this adjustment. Looks clean and simple to me. I also prefer just I would have written it like this: class Foo {
x = "I'm pubic";
# = {
y: "I'm private",
method() { return #.y + this.x; }
}
} How will this behave with the proposal as it is? class Foo {
x = "I'm pubic";
y = () => { console.log(this.x); }; //what is the context of this here?
} *Edited for clarity |
That one is simple. Arrow functions are lexically scoped, so the class Foo {
bar() {
const test = () => this;
console.log(test());
}
baz() {
const test = {
me: () => this,
};
console.log(test.me());
}
}
new Foo().bar(); // output: Foo {}
new Foo().baz(); // output: Foo {} Just as in the functions above, arrow functions declared this way would have a class {
x = "I'm public";
y = () => this.x;
# = {
z: () => this.x,
}
} |
@littledan I would agree with that. It almost feel like a special case for a blank '#' property. It removes the need to use I prefer the syntax class Foo {
# = {
methodX() { ... }
async methodY () { ... }
}
} over class Foo {
# = {
methodX: () => { ... },
methodY: async () => { ... }
}
} But if it's easier because of the lexical scoping as @EthanRutherford described then I would be ok with it. |
@EthanRutherford, I don't think your design would work the way you wanted to, unless it actually did mean something very different from what it appears to. In particular, I think that in class C {
# = {
getThis() {
return this;
}
};
receiverIsPrivateObject() {
return this.#.getThis() === this.#;
}
receiverIsObject() {
return this.#.getThis() === this;
}
}
(new C).receiverIsPrivateObject();
(new C).receiverIsObject(); it must be the case that There's a few reasons for this. The most important is that this is how it would work if you used a public field (e.g. if you So I don't think you can really get the semantics you want, here. But that means that it's pretty awkward to use: you can't readily refer to a public field from the private object, etc. I really do not think that reifying an actual object distinct from the instance where private state lives is a good design. I think it's a lot nicer all around to have private state live on the instance, just as it does in other languages.
See, this hasn't been my experience at all. I explain to people "to declare a private field, begin its name with As an aside, I'm not sure what you mean by "also the name isn't |
I think you misunderstand me. No, I don't intend for this object to have any exotic behavior: it will behave exactly the same as an object assigned to class {
x = 5;
# = {
y: 10,
// accessing both the public and the private members,
// in exactly the way that intuitively makes sense
z: () => this.x + this.#.y,
};
} In this way, there is absolutely no confusing behavior or usage: it's just a plain javascript object. I don't see any way this is more confusing, and in fact is largely identical to the way people have been making their own implementations of private data using WeakMaps. // some-module.js
const privates = new WeakMap();
class Foo {
constructor(x) {
privates.set(this, {x}); // initialization of private data
}
method(that) { // access of private data, including from another instance of Foo
return privates.get(this).x - privates.get(that).x;
}
// it is trivial to show that I can dynamically access, write, and iterate over private data as well
} Also, you could just say "to declare a private field, begin its name with #", but you'd also be lying (or at the very least, withholding the truth). Because the truth is, it doesn't behave like like other fields. Calling these private fields, without also being up front about the fact that their access and write semantics are entirely different is deceptive. We're going to end up with people trying to do things like iterate through the private members, access private members dynamically, write new private members at will within methods, and even dynamically write new private members and these things won't work, and it won't be immediately obvious why. In order to explain how private members work, you need to explain all of it. And I really doubt anyone is going to encounter not being able to iterate through or write new private members and think "ah yes, that makes sense, because they're private". There's no immediately obvious reason that there can't be a private way to iterate through private members. There's certainly no obvious reason I can't add a new private member in a method, either. I can understand the technical reasons, having read much of the discussion here, but I highly doubt that anyone unaware of the implementation details of private members will be able to make those connections. As to the naming comment, it was my assumption that the sigil would not be parsed as part of the identifier token ( But the biggest issue with the name is that this would be an unprecedented change to identifiers. There is nowhere else in all of javascript where the way you name a value impact behavior or meaning. This is not only adding a new concept to javascript, but a concept that fits in with nothing else in the language. Is there even a parallel to the proposal as-is in JS? Is it even possible (without excessive overhead) to transform a class with private members into current-JS, with full spec compliance? |
Ah, yes, I was conflating your proposal with @shannon's, sorry.
Since classes as they exist currently only allow methods, not arrows, I disagree with you about how awkward this is.
Again, I disagree about how confusing it is to have an object's private fields be properties of a different object.
Mm. I disagree that failing to explain every piece of the semantics of something constitutes lying, and have not found most people to be confused by the behavior of private fields. This because private fields behave a great like other fields except that they are private. There are of course details to what that means exactly, but that alone seems to give people an extremely good idea of how they work and let them get on with writing code. If they're interested in how privacy is enforced, I will tell them that it's enforced by the name of the field only being available within the lexical scope in which it was declared, and this name being the only way to refer to the field. People have been teaching a pattern of creating "private members" with privacy enforced by lexical scope for a decade. These likewise do not show in iteration, cannot be created dynamically, etc. I don't think I would call Crockford a liar, here. And the expectations to which you allude don't seem to have meant people avoid teaching this pattern, even to absolute novices. I do not share your beliefs about how people tend to expect private fields to behave.
Well, quite a few of the people to whom I have explained private fields as above have made those connections, so "anyone" is overstating your case, I think.
Yes, it's not a valid identifier character. I don't understand why this means it isn't part of the name from the point of view of the programmer. If your concern is people thinking about implementation details - well, in the spec there is a production called
I contend that to the extent there is a "the" pre-privates-javascript way to do private data, it is the pattern based on lexical scope I refer to above. I further contend that said pattern is much closer to this proposal than to a new notion of a "private object".
Yes, with WeakMaps very much like what you're doing. It's awkward and you might debate what counts as "excessive overhead", but it's doable and matches the spec precisely, as long as you're careful about receivers. You can either use a single WeakMap, as you have, or have one per field, as babel is doing. |
@bakkot if |
Now that I have rethought about it, I think the x = 5
# = {
y: 10,
z: () => this.x + this.#.y,
} My concern is that lexically scoped functions are not optimized for this use case. Each instance would create new functions. This is just antithetical to the idea of prototypes in JS and by extension classes. Needing to iterate/dynamically access/destructure methods is far less common so if this proposal goes through as it is then I will just learn to live with it I guess. Now that I have reread this thread I'm not sure if the comment from @littledan was about my original proposal or the modified version from @EthanRutherford. This thread is such a confusing mess at this point so let me see if I can get back to my original proposal.
The difference here is Say I'm a new JavaScript developer and I have never heard of this proposal and I have no idea Look at this simple example from the perspective of an inexperienced JS developer: class Foobar {
$x = 'x';
#y = 'y';
method() {
return this.$x + this.#y;
}
}
It would have made more sense to do something like In my original proposal here, by not using The use cases for iteration, dynamic access, destructuring may not seem common but it's important to keep the language consistent. If I can iterate over public properties I would expect to be able to iterate over private properties. If I can dynamically assign/access public properties I would expect to be able to for private properties. If I can destructure for public properties I would expect to be able to for private properties. The key here is they aren't variables, they are properties. They should behave like properties first and foremost. When they don't it's a major diversion from what is expected. |
I have an alternative proposal, which while it unfortunately does not address the last issue @shannon referred to, would hopefully address most of the concerns presented by both sides. The syntax would be largely the same as the proposal as-is, but we augment with a few things. This provides the same protections against accidental leakage of data and again, is mostly identical to the proposal as-is, but also provides capability to dynamically read/write and iterate over private data (but importantly, only for instances of the class). The syntax, and therefore the conceptualization, remains the same, but the usage of a internal object-like container allows straightforward avenues for providing dynamic access and iterability. By providing access to these features through the same private interface ( foo(name) {
// clearly not an accident
return this.#getProperty(name);
} |
@ljharb @bakkot @EthanRutherford I have provided an update in the description that I think may address some of the concerns with my proposal. |
Internals slots are purely a spec mechanism; I don't know what observable semantics you intend for this to imply.
I'm sorry, I don't know what this means.
The
We could add some method of dynamically iterating over private properties in a follow-on proposal, but I am really not convinced it's worth doing. I just don't get why you expect or want this.
This is the sticking point for me. There's a few different problems:
class List {
#nodes = [];
static Node = class Node {
#owner;
constructor(owner) {
this.#owner = owner;
}
delete() {
this.#owner.#nodes = remove(this.#owner.#nodes, this); // this is the interesting part
}
};
// etc
} That is, a nested class can refer to private fields defined on its parent, since it has visibility of the field name. But that means it is not clear - even to the spec - whether I continue to feel that a Map (or plain object!) in a private field is a strictly better option than creating new private fields at runtime, in the case that you want dynamically named private state. It's much clearer what you're doing and avoids the issues above. I also fundamentally do not understand why you feel it is so important to support dynamically creating fields. |
I don't think it gets close enough - like I say, anything which has It also has some problems with chained access: class Node {
#data;
#next;
constructor(data, next) {
this.#data = data;
this.#next = next;
}
lookaheadType() {
return this.#next.#data.type; // works in the current proposal
}
}
let head = new Node({type: 'eos'}, null);
head = new Node({type: 'token'}, head); How would you do the equivalent of Similar ideas have been proposed a number of times in the existing extremely lengthy previous discussions of alternate syntax, and have been rejected for the similar reasons. |
This may be true but
I hadn't thought about that use case but with a slight change to the syntax verification you could do something like this: class Node {
private data;
private next;
constructor(data, next) {
#this.data = data;
#this.next = next;
}
lookaheadType() {
return #(#this.next).data.type;
}
}
let head = new Node({type: 'eos'}, null);
head = new Node({type: 'token'}, head); I think of |
It is property access. It's just accessing a private property. I agree that there's a risk with the asymmetry with dynamic access (it's called out in the FAQ), but strongly disagree that it is of even remotely the same level of risk.
Technically, yes, but Very similar proposals have been raised and rejected a number of times previously in the main syntax discussion thread. |
It may have been from old comments on issues, but I believe it's in the faq: "why not As to the code example in your post, I don't see how this is enforced even as is. Who's to say I don't pass in something completely different than a List as the "owner" of a node? How would the access violation in the I don't really see a difference in enforceability whether private fields are preinstalled or not, we still don't know the type of what could possibly be being passed in at runtime until runtime. The answer to the "why" you keep asking is fairly simple: consistency with public fields. As has been stated by both @shannon and myself, what we find objectionable is primarily that these look like fields, but behave more like lexically closed-over variables. What makes it even more complicated is that they're actually even a mashup of the two. In order to allow two instances of the same class to access each other's private fields, they go from lexically closed over fields to more like lexically closed-over keys for entries stored in weakmaps.. or something to that effect. Additionally, there's a concern that, to the unaware, it is not clear that class Foo {
private field = 0;
bar(other) {
return private.field + other.private.field;
}
} But I understand the potential for backward compatibility issues. However, I don't think the argument that |
I'd prefer this syntax because it's closer to anything else we have in JS. And it behaves as I would expect once I know what the |
Sorry it is long but I don't see anything quite like what I am suggesting in there. Edit* nevermind I see your comment in there but I'm not exactly sure why it would be fatal with the |
Attempting to access a private field of an object which isn't present on said object throws an error. If you pass something else in, the
You're proposing that private fields could be created at runtime, presumably in code outside of the constructor. Currently private fields can only be installed during construction, when there is no ambiguity about what it means for something to be an instance of the class - private field creation is tied to instance creation.
I also don't understand why this particular kind of consistency is so important. I do not remotely share the expectation that being able to
Again, they behave a great deal like fields. They are per instance, they are installed by the constructor at the same time as public fields, they are accessed with
The name is the thing which is closed over. I'm not entirely sure what it would mean for a field to be closed over.
Yes, that's a concern. It doesn't seem that major, to me. Because attempting to write
As far as I'm aware |
I just don't get this.
It really, really is not that hard to explain how private properties work in this proposal. I have done it many many times to people of varied levels of experience.
People have proposed |
Is class Foobar() {
#x = 'x';
method(obj) {
this.#x !== this['#x'];
this.#y = 'y'; //runtime error
const { #x } = this; //syntax error
for(const prop in this){ } //#x doesn't exist even though I have access here
}
} At least with
You have explained. They didn't come to the conclusion on how it works based on their knowledge of the rest of JavaScript. I can explain it too. You prefix private variables with This proposal is putting a lot of restrictions in the wrong place. It seems that all the negatives are against the class developer just so there's no chance to accidentally leak. When, as I have described, you can still keep leaking from happening without removing any functionality from the class developer.
It seems the only real concern about this is the syntax for chaining. This seems an acceptable compromise to me to regain all the functionality we are used to. Plus I don't think you really need the parenthesis around |
@shannon Why would Separately, for..in only enumerates enumerable properties, so “i have access to it” in no way guarantees “i can find it in an enumeration loop” - I’m not sure why you have that expectation.
|
@MichaelTheriot it is indeed (conceptually) sugar for WeakMaps; you can put any |
@ljharb I assume you mean |
@shannon yes, my mistake, just typing too quickly :-) I've updated both of the comments you mentioned. |
@bakkot I don't expect engines to use a WeakMap under the hood, but if we agree it's semantically equivalent we should take advantage of it. Can we preserve what this offers? You can access private properties via bracket notation using a semantically equivalent WeakMap as such: const Person = (() => {
const __private__ = new WeakMap();
return class {
constructor(name, ssn = null, cc = null) {
this.name = name;
__private__.set(this, {ssn, cc});
}
get hasSSN () {
return !!__private__.get(this).ssn;
}
get hasCC () {
return !!__private__.get(this).cc;
}
hasSecret (key) {
return !!__private__.get(this)[key];
}
};
})();
const joe = new Person('joe', '123');
joe.hasSSN; // true
joe.hasCC; // false
joe.hasSecret('ssn'); // true
joe.hasSecret('cc'); // false and I would hope the equivalent would be const Person = class {
constructor(name, ssn = null, cc = null) {
this.name = name;
#this.ssn = ssn;
#this.cc = cc;
}
get hasSSN () {
return !!#this.ssn;
}
get hasCC () {
return !!#this.cc;
}
hasSecret (key) {
return !!#this[key];
}
};
const joe = new Person('joe', '123');
joe.hasSSN; // true
joe.hasCC; // false
joe.hasSecret('ssn'); // true
joe.hasSecret('cc'); // false I only favor this because it is intuitive to me. This is confusing:
All of these work with I expected private properties to behave identical to regular properties, just with their reference private to the class internals. What I'm coming away with is something else. This seems more like statically typed variables that can be accessed by I would not expect private properties to be statically typed after a decade of experience, and it feels very inconsistent given that this is not a statically typed language. If this is the intent then private "properties" is misleading in this context and I would suggest a rename at least to convey something more accurate to what these actually are. Sorry if I'm late to the show and what I'm saying is less valuable now, but I have to think there are merits here that need consideration given the small cognitive burden ( |
... Why? It's not always the case that more powerful is better.
The latter. There's some more discussion here and here, though the major thing for me is this: JavaScript currently has exactly one kind of strong privacy, and that is the kind offered by closures. The privacy model of this proposal is pretty much identical to the privacy you get there (i.e., you have access to If I were to think of this as analogous to WeakMaps, I would want to think of it as having one closed over WeakMap per private field, not a single WeakMap storing a "private object".
Can I ask why not? It's certainly intended to: for example,
There's some discussion of this over here, but personally I don't expect it to matter what we choose to call this. People are going to think of it as private fields no matter what we do. And I don't think that's a bad thing, because it seems like it tends to produce pretty much the right mental model.
I don't want to focus too much on the details of this alternate syntax proposal, though I think I linked upthread to some of the previous times it's come up if you really want to read more. Briefly, though, I dispute that this burden is as small as you think: for example, does
They are definitely worth consideration! That said, we've considered all the things brought up in this thread a fair bit already, I think. |
Since '#' would be an operator, I think it's pretty obvious that it's |
@shannon the proposed |
@ljharb Do any other operators behave this way? |
@ljharb In either case if that behavior is desired if that proposal is considering it then as long as it is well defined then the ambiguity should be gone. But to say it's confusing here is to say it's confusing for |
@shannon I'm not aware of any operators which behave as you're proposing Yes, we can pick one and make everyone learn yet another precedence rule, but that's still a cause of cognitive burden which is absent with the current proposal, which is all I meant to claim. |
@bakkot Fair enough, I didn't think it all the way through. Now that I've looked at it again and you're right it should be the way you have described. However, I think this is a small price to pay to reduce the cognitive burden in other ways though. |
Properties are much more than just plain variables and bounded functions.
const validObject = {
[Symbol.iterator]: function * () { yield 5; },
'#': 5,
'': 5,
'#that': 5
}; I do not agree that these can be "properties" if they are not dynamic nor support the above behavior; this is fundamental to properties in JavaScript. At most, these share the access syntax and bind functions to the receiver, but otherwise are just variables.
Thanks for the link; I was not aware of this proposal. To me, this proposal gets closer to the mark with "instance variables" because that is what these effectively are. The mental model is simpler when we stop calling these properties because we lose the expectations that carries (at least, I do). I think it would make sense to interpret these as private properties in the context of a static language, since these generally do not include the extra behaviors of JS properties, but this is a dynamic language, properties here do exhibit this behavior, and this is unnecessary confusion that should be avoided.
I would at a naive glance say it is obvious I suppose only the terminology is an itch at this point. It might seem trivial, but I anticipate others will have similar expectations and slip ups when they hear private properties landed. I have however gotten a better grasp of what this proposal aims for and appreciate the discussion! |
@MichaelTheriot, thanks for expanding. To me, those are (sometimes) things that it happens you can do with properties, but I don't really think of them as being fundamental to what properties are, and I don't think most other programmers do either. For what it's worth:
Not all public properties can be deleted.
True, though part of the point of the more static shape offered by this proposal is that you shouldn't generally have to.
True.
Not all public properties are copied by
True.
I expect you will be able to; see this proposal.
Not really sure what this means. I also want to note that As such, I really don't think that most programmers tend to think of "properties" as things which have all of the above characteristics (and if they do that's a problem in itself); this just seems like a list of characteristics which (some) properties happen to have. For me the most important characteristic of a "property" is that it is associated one-to-one with a specific object, rather than (as with variables) with a specific scope, and secondarily that invoking a function via a property access binds the function's
Some of that seems inevitable, unfortunately. We'd like to do our best to minimize it, of course; I think the current proposal does the best job that reasonably can be done with that without making the feature much less good. Anyway, happy to chat about the design decisions here (and to continue to do so), and I'm glad I could help explain them a bit. |
Most programmers probably do not; these behaviors altogether are pretty unique specifically to this language. Here are behaviors for properties generally not present in most languages (altogether):
Calling something that does not fit this profile a property raises flags to me. If we ignore these and think of JS like a static language, sure, but it simply is not and I anticipate among newcomers and experienced developers alike this will be more confusing than it needs to be.
True, but deleting a public property is never a syntax error like it would be for these. Any mishap in deletion or properties missing in iteration are run-time behaviors explicitly configured by the programmer.
By my examples I meant to convey:
Every restriction I am listing is actually already present on variables.
That is a quirk that I definitely consider a missed opportunity to catch when |
Wait, where does this intuition come from? I have exactly the opposite intuition: the characteristics which are a priori most core to being a "property" are those which are the most in common with "properties" in other languages, whereas details like
Sure, because you don't have enough information to know ahead of time that the property in question is not configurable. Here you do, and we usually feel it's better to get errors as early as possible. This does not feel, to me, like it has anything to do with whether it is "a property". It's just a property which the language is able to make more guarantees about than usual.
I don't really buy "explicitly configured by the programmer", especially for things like |
These JS-specific "extensions" are core to the language. In JS, "static-like" properties are an extension. Naturally, something claiming to be a property but being largely inconsistent with properties as already defined in the language comes off confusing.
I'm an outsider here; I do not know what to say to "we usually feel..." or what weight it carries. All I can say is this is a duck-typed language that is constantly guarding against run-time errors, and this would be an exception to the rule. For what it's worth an avenue does exist to check if a property can be deleted at run-time.
I think it is potentially a big deal to call these private properties, give it a syntax similar to properties, and have no property operators, bracket notation, or descriptor methods work here. The next question is naturally, why is the rest of JS not like this? Why this oddball in an otherwise dynamic language? |
I really do not agree, but I doubt we're going to be able to convince each other here. They are inconsistent in a few ways, but there are already many inconsistencies in how various things called "property" work, and people seem to be able to work with that just fine.
There are dozens of other early errors in the language. For example, Any of those could have been runtime errors, but because there is enough information to statically detect them they're instead syntax errors. I don't think this rule you refer to exists.
Why is it any more of a big deal than to have properties which don't show up in |
Yes, please, let's not forget this! If we're in the scope of the private or protected instance, we should be able to use any existing language features, f.e. Personally, i like the prefix notation, f.e. this.foo
@this.bar // protected
#this.baz // private because, then Object.keys(#this)
for ( const key in #this ) {}
const { bar } = @this which is similar to my inheritance implementation, lowclass, where you can do this.foo
protected(this).bar
private(this).baz
Object.keys( private(this) )
for ( const key in private(this) ) {}
const { bar } = protected(this) We should be able to do literally anything we can do nowadays with public properties. More examples, using notation: const key = makeSomeString()
#this[ key ] = 'foo' The same with Lowclass: const key = makeSomeString() // for example, a UUID, or literally anything
private(this)[ key ] = 'foo' With notation: console.assert( 'bar' in @this ) With Lowclass: console.assert( 'bar' in protected(this) ) At bare minimum, let's let these new features work just like public does, even if they work separately, where by "separately" I mean we can't use for ( const key in this ) {} // only public keys
// we have to do it on them separately:
for ( const key in #this ) {} // private keys
for ( const key in @this ) {} // protected keys With Lowclass: for ( const key in this ) {} // only public keys
// we have to do it on them separately:
for ( const key in private(this) ) {} // private keys
for ( const key in protected(this) ) {} // protected keys Let's let people have the same flexibility they do today with public stuff. In Lowclass, we can also leak protected or private scope purposefully if we want: // FooBar.js
let fooPrivate
const Foo = Class((public, protected, private) => {
fooPrivate = private
private.foo = "foo"
})
const Bar = Foo.subclass((public, protected, private) => ({
test() {
console.log(fooPrivate(this).foo) // "foo"
console.log(private(this).foo) // "bar"
},
private: {
foo: "bar"
}
}))
export { Foo, Bar } import { Bar } from './FooBar.js'
const bar = new Bar
bar.test()
// output:
foo
bar That example is here in the lowclass tests. The following is just a stretch, but maybe it is possible to have even that previous flexibility with the builtin feature: class Foo {
private as fooPrivate // makes a variable fooPrivate in the same scope as the Foo variable
private foo = "foo"
}
class Bar extends Foo {
private foo = "bar"
test() {
console.assert( fooPrivate(this).foo === 'foo' )
console.assert( #this.foo === 'bar' )
}
}
const bar = new Bar
bar.test() or even let fooPrivate
class Foo {
private foo = "foo"
constructor() {
fooPrivate = #this // expose specific private instance
fooPrivate = private(this) // lowclass version
}
}
new Foo
console.assert( fooPrivate === 'foo' ) It would not be possible to do the following: let fooPrivate
Foo.prototype.hack = function() { fooPrivate = #this }
bar.hack() // maybe throws an error
console.log( fooPrivate ) // or undefined in lowclass: let fooPrivate
Foo.prototype.hack = function() { fooPrivate = private(this) }
bar.hack() // error, private is undefined In lowclass, this // leaf of the public prototype chain
protected(this) // leaf of the protected prototype chain
private(this) // leaf of the private prototype chain Any given instance has exactly 1 public prototype chain and 1 protected chain, but has as many private chains as there are number of classes in the instance's inheritance hierarchy. For example, suppose we have this class hierarchy:
In the public prototype chain, In the public chain of the instance, In the private prototype chains, Just throwing the idea out there to show what's possible and to show how (conceptually, not literally) it may be possible to "desugar" protected and private sigils to this multi-prototype form. A current downside of lowclass is that |
Updated the above to use #this.bar // protected
@this.baz // private The Or maybe just #this.bar // protected
##this.baz // private
// hmm, I think I like this. With prefix form, the |
I'm super excited just to have class fields . . . but with that said (yeah poo sandwich i know ;-) ). . . My biggest problem with the field proposal (especially for private), is in 'class' syntax being forced to use the 'this' key word which is completely unsafe. . . as an example:
I'd be fine with requiring '#x', but not 'this.#x'. The previous sytnax would basically for the engine to assume an implied 'this' pointer / object and do the validation for the developer. . it could thrown an error automatically for us when 'this' isn't defined or isn't a direct instance of an given class. . . othewise as developers in order to ensure safety we'd have to do that kind of check ourselves in EVERY single method call. . .which is super cumbersome and painful. . . |
@seansd, the currently specified semantics are that if the engine executes |
OK, seems like @shannon and I agreed in #75 (comment) that it would be fine to pursue this as a follow-on proposal. At the same time, I'd like to keep the current class fields proposal minimal; it's already at Stage 3, so it's somewhat late to go adding additional features. See CONTRIBUTING.md for some details on how to get started. Closing this issue, but feel free to follow up here for help getting started. |
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 ofthis
makes this proposal more consistent with existing paradigms in Javascript.For example:
Would be sugar for:
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.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.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:
vs
Private property iteration:
vs
Destructuring:
vs
Why private in declaration? (I know it's been talked about but I have to try)
First consistency and readability:
vs
Secondly, with
#
being a property ofthis
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 beforethis
to make it clearer to developers that#this
is a lexically scoped variable.Would be sugar for:
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:
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:
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 hereThe text was updated successfully, but these errors were encountered: