js-classes-1.1
js-classes-1.1 copied to clipboard
Document differences from existing proposal
I missed at first glance that this proposal didn't include public fields, and I'm sure there's other things I'm missing too. I think that for the committee members who are familiar with the existing proposal - at least for me, anyway - it would be helpful to have a document detailing the differences in syntax and semantics.
EDIT: These lists are not entirely correct, please refer to updated list below instead.
Using the language of the old proposal, so where I say "private fields" I mean "the thing the old proposal calls private fields and the new one calls instance variables", here's my stab at a list of differences:
- No support for public fields, static or instance (that is, no additional support beyond ES2015)
- Private field declaration syntax is
var x
instead of#x
- Private method declaration syntax is
hidden foo(){}
instead of#foo(){}
- Private field/method access syntax is
this->x
instead ofthis.#x
- No private field initializers
-
static {}
blocks - Adding private fields to a non-extensible object is forbidden (https://github.com/zenparsing/js-classes-1.1/issues/14 / https://github.com/tc39/proposal-private-fields/issues/69)
- Accessing a private method does not do a brand check, I think (not clear if
({}->hiddenFoo())
would throw) - It is an early rather than runtime error to assign to a private method
Things that are similar:
- The privacy model, I think - it's not totally clear to me whether nested classes inherit visibility of fields the way they do in the current proposal
- Support for instance private fields, methods, and accessors
- No support for static private fields, ~~methods~~, or accessors (edit: I guess static hidden methods are supported, looking closer)
- Runtime brand checks on private field access (but not private methods?)
Things I am not clear on:
- What happens if you use the return-override trick to add a private field to an object which already has it? That is, what does
let o; class B { constructor(){ return o ? o : this; } } class D extends B { var x; constructor(){ super(); o = this; } } new D; new D;
do on the second invocation ofD
?
Thanks @bakkot for putting together this list.
First, I want to be really clear about language: it is essential that we not use the term "private field" to talk about instance variables, because one of the primary goals of the proposal is to avoid conflating own properties and instance variables under a blanket concept "fields". A similar argument applies for the term "private methods".
Adding private fields to a non-extensible object is forbidden
We briefly had this in place, but it has been removed. (I just updated the rationale document to reflect the current status.)
Accessing a private method does not do a brand check, I think (not clear if
({}->hiddenFoo())
would throw)
That is correct.
It's not totally clear to me whether nested classes inherit visibility of fields the way they do in the current proposal
They do. As in the private field proposal, everything inside of the class body has visibility of the hidden names defined in the class body.
No support for static private fields, methods, or accessors
Again, we use the term "hidden method". Any method (including accessors) can be hidden
, and any hidden
can also be static
.
What happens if you use the return-override trick to add a private field to an object which already has it?
As is (I believe) the case with the current fields proposal, a TypeError will be thrown if the instance already "has" the instance variable.
First, I want to be really clear about language: it is essential that we not use the term "private field" to talk about instance variables, because one of the primary goals of the proposal is to avoid conflating own properties and instance variables under a blanket concept "fields".
I get why this would be desirable, but I think it isn't something we can actually do. All TC39 has actual authority over is the syntax and semantics of the language, not how people refer to parts of it, and I guarantee people will continue talking and thinking about "public and private fields" no matter what language the proposal uses or how we try to teach it. I'm going to continue using the language myself in the context of this thread, since this list is intended to explain things to people who are familiar with the language of the old proposal, and since#x
of the old proposal and var x
are pointing at a thing with very similar semantics and use cases which needs some label.
Thanks for the corrections and clarifications. Here's an updated list:
Differences:
- No public fields, static or instance (that is, no additional support beyond ES2015)
- Private field declaration syntax is
var x
instead of#x
- Private method declaration syntax is
hidden foo(){}
instead of#foo(){}
- Private field/method access syntax is
this->x
instead ofthis.#x
- No private field initializers
- Addition of
static {}
blocks - Accessing a private method does not do a brand check
- It is an early rather than runtime error to assign to a private method
- Addition of private static methods and accessors (though because of the absence of a brand check these differ from instance equivalents only in their home object)
Similarities:
- The privacy model
- Addition of instance private fields, methods, and accessors
- No static private fields
- Runtime brand checks on instance private field access
- Adding a private field to an object which already has it throws
So in this proposal, hidden instance methods do not do a brand check, but hidden instance variables do?
@ljharb see hidden method rationale
Hi @bakkot , great list. Thanks for gathering it together. I'll use it to briefly state my current positions and point to the issue where this was discussed in more depth. Despite the following criticisms, I am strongly in favor of this proposal.
Differences:
- Private field declaration syntax is
var x
instead of#x
See #7 . In the current state of this proposal
{
var x;
in one context would mean something completely different than
{
var x;
in another context. I find this terrible.
- Private method declaration syntax is
hidden foo(){}
instead of#foo(){}
See #7 again
Both hidden
and var
are driven by community revulsion at #
and even apparently at any sigil at all. My hypothesis is that those who object to any sigil want to use dot alone for access. Given binary expr->name
on usage, how much additional revulsion would there be for unary ->name
on declaration?
I liked an earlier version that had hidden methods just be concise method syntax prefixed with ->
before the method name. I would also put a ->
prefix before the declared instance variable name.
The current proposal introduces a sigil, an overloading of the meaning of an existing keyword, and a new keyword. The alternative explained here introduces fewer new syntactic elements, but uses the introduced sigil more often. Would large numbers of people really find the first simpler than the second?
- Private field/method access syntax is
this->x
instead ofthis.#x
Awesome improvement!
- Accessing a private method does not do a brand check
This makes me uncomfortable, but I understand the reasons for this. I reluctantly agree.
- Addition of private static methods and accessors (though because of the absence of a brand check these differ from instance equivalents only in their home object)
Accessors also differ from methods in how they are invoked.
I am glad to see more work on these proposals - I appreciate the immense political & technical effort these features require.
Thank you all.
I couldn't see anything in the rationale.md on this: Is it really a good idea to use ->
as an accessor? - it's so similar to fat arrow functions () => {}
I assumed skinny arrows would be used for non-lexically bound functions.
💯
Is it really a good idea to use -> as an accessor? - it's so similar to fat arrow functions
Yes, we did consider this point, and we also considered "stealing" ::
from the bind operator proposal: https://github.com/zenparsing/js-classes-1.1/issues/19#issuecomment-371250625.
Seriously though, >>alright = 'maybe'
or |>alright = 'maybe'
Noticed some of that here: https://github.com/zenparsing/js-classes-1.1/issues/19#issuecomment-371263615
Ugh, I keep coming back to: do we really need privates in JS?
I know, there are use cases, and it's cleaner than alternatives - but JS has made it this far w/o this stuff.
One thing I'm really curious about:
What percentage of advocates for the private field/method/members use TypeScript or flow already?