-
Notifications
You must be signed in to change notification settings - Fork 113
Another argument on why this might not be a good idea #272
Comments
code inside functions already has no such guarantees; I’m not sure why this line is different. |
It does until you see something with a pair of braces. Take this example:
If there's not a pair of braces or function inside, there's no way something will happen later, more than once or not at all. |
const x = false && willNeverHappen();
await willHappenLater();
await Math.random() > 0.5 ? willMaybeHappenLater() : willMaybeHappenNow();
while(true) noBracesHappensManyTimes(); |
@fabiosantoscode Could a minifier first move all the static declarations to the top, so that then the order is linear? class A {
[bar()] = 1;
static [foo()] = 2;
x = 3;
static y = 4;
} -> const key1 = bar(), key2 = foo();
class A {
static [key2] = 2;
static y = 4;
[key1] = 1;
x = 3;
} |
//Well known short-circuit evaluation caused by operator &&
const x = false && willNeverHappen();
//Does indeed happen later, but you're waiting on it, so it's effectively happening now.
await willHappenLater();
//Implied braces as `x?y:z` === `if(x){y}else{z}`
await Math.random() > 0.5 ? willMaybeHappenLater() : willMaybeHappenNow();
//Single line statement fully equivalent to `while(true){noBracesHappensManyTimes();}
while(true) noBracesHappensManyTimes(); The point is that in each of your examples, it is either a 1-line concession that allows braces to be omitted, or an operation with implied braces due to a keyword or operator. The same thing cannot be seen in the more comparative lexical declarations. That's the point. It's rather peculiar to have code with a completely indeterminate execution sequence in line inside a declaration. @nicolo-ribaudo |
@rdking saying "well-known" is a bit misleading; class fields will become well-known in time too - otherwise nothing new would ever be added because only old stuff is "well-known". Note that this also works: while
(true)
/*
some long comment
*/
noBracesHappensManyTimes(); so it's got nothing to do with "1-line". Certainly the braces are implied - in class fields, the "delayed execution" is also implied. |
@ljharb No line in your example will be executed before a line above it. All statements will be evaluated in order. |
@a-ejs certainly. but since class instance fields have different semantics, and a class body is not a statement list like normal code, an expectation that it works like normal code is incorrect. Whether it's intuitive or not is subjective, of course. |
Of all of the aspects of this proposal, I'm very confident that this one will be fine in practice, because we have very wide experience with this syntax in Babel and TypeScript over the past few years, and have seen that developers are happy with it in general. |
It might be anecdotal, but for me personally, I wrote code like this: // contrived example
class SomeComponent {
constructor() {
this.state = {
someState: { someMoreState: 1234 }
}
}
} Instead of using class fields in React with Babel, thinking that if I added the class field the It was my intuition that the class field would only be evaluated once and used multiple times, even though I've been using this syntax since 2016 (or earlier, I cannot recall). Another problematic piece of code in terms of readability because of this issue is: // warning: there shall be multiple symbols here!
class Foo {
somethingUniqueToThisClass = Symbol('i am unique')
} |
While it might be true that in the case of It might be intuitive for people who write things like Java, where this syntax feels OK, but for me coming from python this doesn't make much sense. Here's me creating a >>> class Foo(object):
... something = print("I got evaluated!")
...
I got evaluated!
>>> Foo()
<__main__.Foo object at 0x7f2a150f15d0>
>>> Foo()
<__main__.Foo object at 0x7f2a150f1650> |
And here's some ruby too. irb(main):001:0> class Foo
irb(main):002:1> @something = puts "I got evaluated!"
irb(main):003:1> end
I got evaluated!
=> nil |
Python also has a bad implementation of default parameters. JS reevaluates the parameter every time. Python's precedence is absolutely not what we want to use.
This example is using a class instance variable, which is like a static field in JS. Ruby doesn't have a declarative way to define instance variables, it's done imperatively during construction. |
Oh, my bad. But there's probably a good reason ruby doesn't allow for class fields. I would say it's the same issue I'm talking about Not implying that it's a programming language or anything, but PHP, while it allows for these class fields, greatly restricts the feature, only allowing for numbers, strings, arrays and little more. |
with this new proposal, not anymore it isn't. But currently 'function code' can only be found in functions, that's a very reasonable expectation to have in a language like JS, and to me breaking this rule sounds like a big deal. (and that's just one issue this proposal has) |
Thanks for expressing my concern better than me :) |
You're right, if we get stuck with class-fields, it will eventually be come well known that certain current day programming paradigms will be useless in the face of "fields". However, that doesn't make anything I've said misleading. The prototype problem has existed in this language since the beginning and has (at least to my knowledge) been a well-known issue since ES3. This means that even noobs get introduced to it fairly quickly one way or another due to proliferation of existing best practices. The simple fact is that the out-of-sync execution of "fields" is going to prove problematic for anyone not familiar with the concept simply because it is not intuitive. It doesn't fit the general workflow of ES. It doesn't fit the general usage patterns of ES. It doesn't even fit the general structure of ES. It's a completely foreign concept to the language. When the wider audience becomes aware of it (and after moving to NC, I'm aware of just how unaware most ES programmers are about this), there will be issues until sufficient time has passed for the community to adjust to the loss of existing good practices and adopt new ones. |
As far as i know, it’s been intuitive to everyone who’s used the feature via babel for the last 4+ years, including much of the react community - which i suspect is more syntactic evidence of intuitiveness than almost any other language feature has had. If there’s been any feedback over the years from that large community that this has been confusing, I’d be very interested to see it. |
Over the years behind this proposal, many of us have tried to show you that confusion by introducing large unaware populations to this proposal, and then polling them after they've learned it. However, when those polls were brought to light in the various threads, TC39 (yourself included) dismissed them with arguments about the invalidity of design by polling/popularity/etc. Then the very next thing would be an evidence-less comment much like the one you just made. It's not that I think you're being disingenuous, but rather quite a bit too general. Really? "It's been intuitive to everyone" without exception? I've used babel many times over the last 4 years. I've also used React. And the 1 thing I can say is that both of them, where fields are concerned, used
For all my experience with ES, that was completely outside my expectations. It almost made sense when I thought about it in terms of how Java classes work, except that with Java classes, each class in the inheritance chain has its own storage space within the instance object. With the way ES handles Long story short, class-fields is attempting to apply a concept to ES6 So.... please downgrade your absolute "everyone" to a more reasonable, indefinite "many". |
Fair enough. |
So, I just wanted to comment on here because it seems like this discussion keeps going, with some very vocal critics and maybe not many testimonials from actual class fields users. I've read through a lot of the criticisms of this proposal, and I understand them pretty well at this point I think. In isolation, I think there are two good points:
However, when considering the full design process, I believe that the decisions for the behaviors in this spec make a lot of sense, and are (counter to the claims of the critics) very much informed by real world usage and experience. I think the core of all of these disagreements comes from this statement on #150:
This is one of the main starting points for many of the final decisions in this proposal, IMO. If public fields aren't necessary, as #150 claims, then the design space does open back up. Ideas like those proposed in Classes 1.1 and #264 become possible, and even make some sense. However, if public properties/fields are required, then the private syntax that is proposed really does become the least-bad option, as it makes a lot of sense to make sure that public and private fields behave similarly and work together. Indeed, this was the whole reason why public class fields were not shipped on their own, in isolation, in the first place. So, let's examing this claim. Are public fields/properties unecessary? And does their behavior cause pernicious bugs? Are public fields unecessary?A lot of the counter arguments center on the idea that since fields are syntactic sugar for execution in the
These all come down to having a way to specify a class element which is not an accessor or a method, be it public or private. The ability to do this in a declarative way already shows a lot of promise, and I believe between potential future modifiers (such as maybe an As a thought experiment, we can try to imagine other languages that have classes or class-like constructs such as structs without the ability to declaratively define their elements. Imagine Java/C++/Python classes, or Go/C/Rust structs, without the ability to declare their elements (note, not assign, just declare). Even Ruby, though it doesn't have the ability to declare fields directly in its class definition, does have a dedicated syntax for them which allows them to retain much of the benefits (statically analyzable at definition time, tooling support, etc). I think the value of declarative element definition is pretty clear in all of these cases. My personal experience is that this has been invaluable. It makes the shapes of objects much easier to understand and work with, and the tooling support and the ability to do meta programming via decoration (in, again, a declarative way) has made day to day work within JavaScript much less error-prone and easier. For these reasons, I think that the ability to declare public fields/properties is absolutely a necessary thing to add to the language, in some form. I would love to see counter-proposals with alternatives to the current private syntax that start with this assumption, and design in such a way that their private syntax would not interfere with this ability. I believe that both Classes 1.1 and #264 would not do this - that is to say, they would sacrifice the ability to declare public properties in order to have a slightly better (from some perspectives) private syntax. Does class field assignment cause bugs?So, if we accept that public fields declarations are a requirement, then the next question becomes: How should assignment work? There are roughly 3 possibilities:
The issues with instance assignment are well known, and in my experience, they are not wrong either. This is definitely a confusing thing the first time it is encountered by users. I think both However, I have also had direct experience with the alternative of assigning field values to the prototype. We built a class model with these exact semantics in Ember.js, and have used it for almost a decade now, and the amount of bugs and other issues this has caused is massive. In part, it's counterintuitive to how users think a class should work, and it also leaks constantly, with code beginning to actually rely on the shared state (once users figure out that it does that). We even have an ESLint rule to prevent this specifically for this reason. Static fields enable the same pattern, but they do it in a way that is much more obvious that it is happening. This also allows users the ability to choose, instance objects/arrays, or static objects/arrays, and this is very valuable in the common case. Finally, not allowing assignment at all prevents The main point here is, all possibilities for assignment contain pernicious bugs and issues that users will have to learn. The only possibility that does not is no assignment at all, just declaration, and that severely limits usability and expressiveness. The proposal currently does the opposite - it accepts some pitfalls that can be learned and avoided, in favor of the most expressive option that enables the most functionality. ConclusionAs a framework contributor who has worked with class fields and decorators for a few years now, I find their functionality to be very well thought out and designed. Yes, there are pain points, but they were not unneccesary pain points - they were the result of prioritizing certain functionality, such as declarative public fields, and matching private state, during the design process. This was done with much community feedback from many different early adopters - Ember.js, Typescript, React, Salesforce, etc. etc. And ultimately, I think that the solution presented really does choose the least-bad (and in many cases, the best) solution that matches all of the various constraints. In my experience teaching users about the gotchas, they are generally pretty easy to learn and they move on fairly quickly. They haven't limited our ability to write classes in our frameworks or caused major issues either, and I don't expect them to. Finally, I also just want to reiterate in closing, I do understand the criticisms and I think everyone who has contributed to all the various threads on this repo has valid points and concerns. I don't mean to come off as dismissive of them, rather, I'm mainly making this comment because I haven't seen many users from the community defending class fields and their benefits on here (which I suspect is because they have been working well for many folks, and they don't feel the need to check in), so I wanted to add that perspective to the conversation. |
Thanks for your detailed rebuttal. I would however argue that:
Something which would remove the ambiguity about when field values are evaluated, would be something like C++ has, which is the definition of what the arguments are after the closing parens of the constructor. It's a bit hideous, but it would look a little something like this:
Besides declaring the fields, this even allows you to define their values according to the constructor parameters. You can also directly destructure objects and shove values into the fields. By knowing the types of the arguments to the constructor, tooling would know the types of the arguments and assign these types to the fields by extension. It's not the prettiest syntax in the world but it makes it pretty obvious that it executes multiple times. |
It is possible to create heuristics for knowing which class fields exist, but it is not possible to know all possible fields without evaluating the constructor. Consider: constructor(...args) {
setupMyObject(this, args);
} Because the Additionally, this method would not allow for decoration/modification in a performant or declarative manner, e.g. it wouldn't be possible to add
Absolutely! And as I pointed out in my post above, I would love to see counter-proposals that attempt to explore this space. The most popular counter-proposals so far seem to avoid doing so, in order to prioritize what they see as a more ergonomic private field syntax. The syntax you proposed does seem interesting. I feel like it would change a different norm in the language - that nothing comes between the closing paren and the beginning curly bracket of a named function definition. This may be confusing too, and I wonder if it would parse well, etc. But it's worth exploring, for sure. Edit: Additionally, is there precedent in other languages for something like it? It's always useful to look at other designs to see what the pitfalls were, and how well it works 😄 |
FWIW, I've found in actual practice for about three years now that the teaching curve here—which is one of the things people seem to object to most—is minimal. I've been helping people come up to speed with this proposal for a couple years now via TypeScript (including having taught conference workshops on TypeScript to groups of devs who included people who had never even seen Let's make that very clear, over and against claims that it's merely a matter of people who've already learned it who find that it's fine: in the process of my teaching it, it has never caused any confusion. What's more, as @pzuraq noted above, it's a much better solution in this space than the existing alternatives, which historically have tended to use mixin-style prototype extension, or explicitly doing |
@chriskrycho As one of the dissenters, I don't believe I've ever argued that the learning curve was difficult. It definitively isn't. Instead, I've always argued that the implementation is not intuitive, i.e. it requires an explanation. Most of the other language features don't in fact require much in the way of explanation. ES is very much in line with other C-style OO languages. So knowledge of those other languages ports pretty quickly with minimal explanation. Let's make this very clear: there is no point in adding a feature in a non-intuitive way when an intuitive approach exists that renders results more in line with current best practices and usage patterns. @pzuraq First, nice post. You've really thought through this. However, you missed on a few points.
Splitting the declaration and initialization like this preserves TC39's desire to use My point is simply that for all the problems this proposal will cause, there is a viable solution in a different approach that will otherwise render precisely the same observable semantics. The depressing fact is that TC39 was either unaware of or failed to come to consensus about any of those approaches. I'll say this: This proposal will no doubt prove to be useful in a good way for many developers. However, it will also prove to be disruptive and problematic for many others, even those who aren't directly using the features it provides. It's this second thing that's the core problem. |
With Set or Define, fields would work identically, for the record. |
@ljharb Your statement is confusing. Assuming you're talking about my point 4: if Define semantics are used in the constructor, the prototype chain gets shadowed. The point of moving the declaration to the prototype is so that it can be defined in a way that doesn't cause prototype chain shadowing. The constructor-time assignment should not use Define semantics. If that's not what you were referring to, please clarify. |
@rdking thank you! And thank you for your response, I appreciate the dialogue 😄 it's also clear you've thought through these things too, and care a lot about them.
I feel like this is a bit of a reach. It has a lot of similarities to many other languages, some C like ones, and many others as well. There are enough differences that most developers will have to learn a few things, as with any language, so I think it's reasonable to say that things may work a bit differently in this language. There are certainly plenty of things that aren't intuitive at all, such as the binding behaviors of
This is true, but this is also true about all language features, including any possible private state solution. I feel like the fact that we're all here discussing future language features means it is a foregone conclusion that we would all like to see the language evolve (and if not, all language changes are backwards compatible, so of course it should be possible to continue using older styles indefinitely).
This is true, and it would be nice if we could transition to a different inheritance model, but again I think it's ok for behaviors to diverge somewhat between languages. The overall semantics are similar, which is why the same keywords were chosen. The behaviors are a bit confusing, but ultimately I think most people pick them up well. This is mainly working with what we have, in a non-disruptive way. If we can't make improvements to the language because we can't match other languages exactly, we won't be able do much in the end. I think the other features that TC39 has shipped, classes included, show that there is plenty of value in adding new features despite differences and quirks. Edit: Thinking on a bit more, I think another important thing that makes this "make sense" in the way that you mean in Java, C#, and C++ is that their classes cannot have arbitrary properties added to them at any time. If they could, then it would lead to split of behavior, with some fields/state living in their inheritance tree and some living only on the instance.
I am aware, Ember has had similar meta programming capabilities wrapping prototypes for some time. However, we need a language level solution in order to have shared tooling, and other shared features like decorators/modifiers.
I believe the first solution here is effectively the same as option 2 from my first post. Even if a value were assigned to the prototype, the assignment in the constructor would be done on the instance, and wouldn't take the value in the prototype into account at all. The second solution seems plausible from an implementation perspective, but I imagine could be the source of many hard to track down bugs. Users would have to be aware that sometimes a value will interop nicely with the prototype chain, but other times it will not. That seems worse that always choosing one or the other.
Can you expand more on why you believe this will be problematic for users who aren't directly using these features? It's true that superclasses could potentially override subclass behavior, but that's already true today with classes and class constructors (and something that framework authors have to be aware of, we've had plenty of experience with this in Ember). Do you think that subclassing library/framework classes will be cause lots of disruption? Can you give some concrete examples of the type of disruption you would not want to see? |
@EthanRutherford The syntax itself isn't too surprising. However, the semantics are. Consider the following: class Base {
alpha = 1;
}
class Derived {
alpha = 2;
print() {
console.log(this.alpha + super.alpha);
}
} It's rather surprising that this does not work given how much work was put into making fields behave as they would in a compiled language. It isn't as though it's not possible. I just recently built a library called ClassicJS that does exactly that. It could have been even better if I had the ability to implement this in-engine. The problem is that this is only 1 of many cases where the method of shoehorning in compiled language semantics breaks in the face of ES' prototype-based nature. What about this: class Base {
alpha = 42;
}
class Derived {
alpha() { /* do something */ }
}
(new Derived).alpha(); // Throws! I'd say it's pretty surprising to find that |
To my knowledge, neither of these two cases would work in a compiled language either. Overridden parent methods can be called from a child class in some such languages, but I've never seen anything with such a concept as shadowed parent data being possible, let alone accessible. All languages I know of would override the value, and that's what I would expect. As for the second example, again this is not possible in a compiled language: you'd get a syntax error of some kind. While the result here would not be what we're necessarily expecting, I'd also say this is very clearly a logical error by the author. If we're following OO practices, the base class sets up a contract, and attempting to change the value of
I wouldn't want the above to work in the way you suggest it should, because then code operating on the I understand you may not come from an OO background, so I'd just like to communicate that, as someone who does, these are by and large the semantics I expect. |
@EthanRutherford Sorry about baiting you like that. I figured the direct approach was too easy to argue against.
Nice work. You caught it. Yet you still missed it somehow. Why? Simply because this is exactly how JS works normally. ES is a prototype-based language, not a compiled one. Trying to force in compiled language behavior causes somewhat or completely surprising result, especially when only half-done. In the first case, ES makes no real distinction between the types of data stored in a property of an object, and a prototype is just that: an object. As such it doesn't make a distinction between keys with let a = {
__proto__: {
alpha: 1
}
alpha: 2,
print() {
console.log(this.alpha + super.alpha);
}
};
a.print(); // 3 and this is what is both possible and reasonable to expect from an implementation of In the second case, since a derived class does nothing to check the members of a base class, you cannot expect that developers will not do something like this. Am I saying it's good design? Heck no? But if you're going to go so far to work around a well-known, easily managed foot gun, you should also put in work to prevent things like this that will produce obviously hard to find errors. The usual case is that This is yet another reason why the prototype is a better choice. You said it yourself
That contract is the prototype. If we're going to rely on engine magic to store a portion of that contract in a concealed location, then we're going to need to rely on even more magic to verify the contract is being upheld. That simply isn't currently the case. And as such, for every somewhat rare time someone makes this mistake, there's going to be a surprising error waiting for them. That was fun. I haven't tried bait and switch for a long time. I wasn't sure you were going to bite. I'm curious about your opinion on something though. What about this case? class B {
A = class A {...};
...
} Should there be multiple versions of |
BTW, I wasn't around when OOP was born (SmallTalk), but I did have the pleasure of watching it grow up (C++), watching it experiment with interesting ideas and mature(PHP, Perl, Python, Object Pascal, Object COBOL), only to see it used (C#) and abused (Java) only to be partially (ES) or completely (recent functional languages) abandoned by its users. It's definitely a good thing that more OO structure is coming to ES. However, TC39 needs to be exceedingly cautious about how it carries out their designs. Trying to shoehorn in features that belong to compiled languages while not taking the proper precautions to ensure safety with prototype compatibility in a prototype-based language is pure folly. But that's precisely what this proposal is doing. It takes the popular "best practices" of today and codifies them while simultaneously writing off as insignificant the core nature of the very thing being enhanced. Having 2 sources of truth for a single class is not good, especially when they can conflict in the very ways you pointed out. One of the members of TC39 said that they cannot fix the prototype foot-gun without essentially changing the language. That's true. However, causing the language to drift away from its prototype-based nature by introducing an unarguably new and partially incompatible means of creating a template changes the language all the same. |
Yes. If I wanted a single version, I'd use the |
Well, I can't say I disagree with you on that. However, I also recognize that there are sometimes circumstances (usually due to a bad design somewhere) when being able to have an "internal" class that is consistent across instances is useful. Static won't be able to give you that unless it's also private. Where we disagree is in that there should be multiple instance of My point remains though. Having 2 sources of truth for the contract provided by a class is folly. Either the contract must exist in the form of a prototype, or the contract must exist in the magic internal format. Splitting it between the two is what's causing issues. |
It's already split, because JS constructors have always been able to (and have done, in the community) install properties on The perspective you have - that the prototype is "the contract" or the "single source of truth" - is totally fine to apply to your own code, but is simply not something guaranteed by the JS language - ever, or now; by the spec, or by the community at large. |
Seriously? The constructor is not a source of truth for what the class provides. It's merely another function that just happens to run before anyone receives the instance. That function could very well do something like this: class Ex {
constructor() {
if (Math.random() > Math.random())
this[Symbol()] = Math.random() * Number.MAX_SAFE_INTEGER;
}
} What's the truth there? All you know is that some Symbol is being applied. You don't even know the value of that Symbol. Nor can you claim that it exists on all instances. Contrived? Yup. But it proves my point. I've seen code where the constructor applies externally defined data to the instance using externally defined keys. That's just as bad as what I've done here. My point? Initialization does not provide a source of truth. If it's not part of the class definition, it's not guaranteed to exist. Therefore, prior to this proposal, the only source of truth for an instance is its prototype. |
The constructor could mutate the prototype, or shadow something on the prototype. There simply is no source of truth for what a class instance looks like except reading the code and knowing what it does, and this proposal doesn’t change that (except to make reading the code easier by making more things declarative) |
... and change the truth of all instances
... altering itself locally while still containing the truth in its prototype. I'm surprised you didn't bring up the fact that the constructor could swap out or completely remove the instance's prototype. Not that it would have helped your argument. Changing the instance's prototype changes the class it belongs to, and therefore the truth about the instance.
Incorrect. If |
No, it’s not the truth universally - you’re simply incorrect here. |
A statement with no evidence? C'mon. That's not your style. Should I give you a hint? There's only one counter argument for what I just claimed. However, you previously decried it as bad practice. |
“is an instance of” also has no concrete definition given Symbol.hasInstance, so I’m also not clear on what you’re claiming. But if you claim a counter argument exists, then automatically you’re claiming it’s not a universal truth ¯\_(ツ)_/¯ |
You just named it. Symbol.hasInstance is capable of changing the outcome of My point? Even without universality (which was only broken relatively recently), prototypes are the closest thing you've got to a source of truth for what can be reasonably guaranteed to be found in an instance of a class. BTW, where did I claim the source of truth to be "universal"? Don't remember making that claim. |
This statement is false, because “the closest thing to a source of truth” is objectively not the same as “the only source of truth”. This means it’s not an axiom of the language that’s being changed, only an axiom of your personal mental model. |
Let's look at the possibilities prior to this proposal:
Of all the possibilities, the only one that consistently provides the same properties to all instances is the prototype, hence "the only source of truth". Nothing else comes close enough to even apply. So let me re-state my assertion more clearly: For any non-exotic object |
Even setting aside the edge cases, all you’ve got is that “if it is in the prototype, it is in the instance” - the reverse isn’t true. |
The reverse isn't true even in compiled languages. Just because it's in an instance doesn't mean it has to be part of the template. The only guarantee is supposed to be that if it's in the template, it's in the instance. It's a 1-way relationship. |
And, the same is true for public fields. It's not on the prototype, just on the instance. Exactly as you say - "Just because it's in an instance doesn't mean it has to be part of the template". Thus, just because it's an own property doesn't mean it has to be on the prototype, to use your own definition of template. |
That's the catch. Those public fields are part of the class and yet not part of the class template. That means there's a separate source of truth. Once declared, it is guaranteed that the resulting instance properties declared by the fields will exist prior to the first post-super line of code in the constructor. Such guarantees are the point of having class in the first place. My point? A |
So too are all |
Red herring fallacy. Static things are not part of the instance. A class does indeed provide guarantees about the initial state of the constructor function. However, that's a slightly different topic. |
It's not a red herring or a fallacy. Class bodies currently contain two kinds of things: the constructor, and methods, which have a "placement" of static or prototype. This proposal makes class bodies contain a third kind of thing: fields, that are private or not, and also have two possible placements. Nothing about this is a different topic. You continue to claim that everything in the class body defines the shape of the instance, but that is false - being inside the class body offers no inherent guarantee that it's related to the instance at all. This proposal doesn't change that. |
True, but wrong things.
Till now, ES
Over generalization fallacy. I never said "everything in the class body", did I? I've chatted with you enough to understand why you'd make that leap, but it takes you to the wrong place. A class body defines 2 things using the things it contains:
This proposal seeks to add a 3rd thing to this list: That's out of scope for every existing class in every language. Sure, the semantics have been worked out to resemble what compiled languages are doing, and that's great. However, it ignores part of the nature of a class in doing so. Specifically, the part that says things defined by a base cannot override anything defined by a derived. From where I sit, that's a death knell. You killed the very thing you were trying to enhance. |
I'm going to pop open a different thread. I want to share with you a way to fix this without giving up any of the decisions made by TC39. |
prototype, not instance. |
Thanks, fixed. |
The design of this proposal was largely based on prior work by TypeScript and Babel, which have included fields with this syntax for many years. It seemed to us in TC39 that many developers were happy with these designs in terms of developer intuition, even though they had the property described in the original post. This proposal has been at Stage 3 for years, and is shipping in several JS implementations, so we will not be making a change based on this thread. |
I've been implementing this proposal within Terser, to make sure people can minify their modern code. However I'm haunted by the complexity of it (when trying to implement it inside of a minifier that does the same basic assumptions of ES all over).
This proposal, which provides some neat syntax sugar, has a very bad readability problem to me and probably newcomers to ES.
The problem lies with execution order and execution count. Consider this example:
It's obvious that
doSomething
is called immediately, howeverdoSomethingElse
is called zero, one or many times, depending on how many times the class is constructed.This breaks the basic assumption that code that's not inside of a switch statement has some pretty linear ordering guarantees. That something inside of a pair of braces is guaranteed to be executed in sequence, not at all, or many times. Not that parts of what is in a pair of braces are executed once, and other parts are executed however many times something happens.
This is very unreadable to me, and even though I've enjoyed the sweet sugar using Babel I must denounce this as something that makes ES harder to read and understand.
Thoughts?
The text was updated successfully, but these errors were encountered: