I think I'm beginning to understand your perspective here, and yes, I was referring to the [[Define]]
behavior. I'll repeat an argument that I posted regarding this choice. When creating a lexically defined object:
let x = {
...
};
it makes sense that the engine uses [[Define]]
semantics to add properties on the new object since the object itself is a direct production of that syntax. Likewise with a class
definition, it makes sense that the declared static members use [[Define]]
semantics on the constructor, and the declared non-static functions and accessors use [[Define]]
semantics on the prototype, as the constructor and attached prototype are direct productions of the class
syntax.
Class instances are not direct productions of the class
syntax. As such, class instances should not be subject to [[Define]]
semantics imposed by the class
keyword. Until this choice was made, it was always the case that only direct productions were created using define semantic. Violating this has created a situation where expectations of standard inheritance rules can be violated.
What makes this even worse is that the creation of field initializers completely side-stepped the need to avoid the prototype for the sake of avoiding the "object on prototype" footgun. As such, [[Define]]
semantics could have been used on the prototype, and set semantics on the instance, thus preserving all expectations regarding inheritance... at least in the public case. Something similar could have also been done for the private case as well.
TL;DR: Since the class body of a class containing non-static public fields is not completely formed until all define statements generated by the class syntax have been completed, it cannot be said that these elements:
As for the private field case, I would agree that this is external to defining the class body. I was not limiting my arguments to that scope. I will do so for future arguments. I hope this clears up my understanding of this for you.
That's not necessarily a valid assumption. Consider the case of class A
being defined in a function that is called multiple times with different parameters. This is not uncommon. Consider the case of class A
being defined by an eval
statement or Function()
call. These might also print the log multiple times depending on how they're triggered. Claiming that there is a strict expectation of single execution is figuratively handcuffing developers, and limiting them to use cases you expect. I have personally written code using both of those alternative scenarios.
However, for the sake of argument, let's stick to the most common scenarios, where your description makes the most sense. I still can't say that I agree with your assertion. From my perspective, the expectation is that an initializer is used once to initialize its target field. This is subtly different from your assertion in that it accounts for the initialization of non-static fields as well. Those are most certainly expected to be repeated on each instance.
I think the problem arises because of the difference between static and dynamic languages. In a static language supporting class, it could be guaranteed that all classes are compiled at the same time and their corresponding structures set before the code has a chance to mutate anything. We're not so lucky. What makes this worse is that each function in the prototype chain of a given class is it's own top-level object, and subject to be used independently of the functions that extend it.
This is why my thought is to treat each derived class as if it were it's own island, and the definition of the base class were simply a mix-in, applying all fields and properties of the base to the derived and initializing accordingly. I would also think that it is ok for the decision of whether to use this behavior to be left to the derived as this would be more consistent with the behavior of static languages supporting this feature. After all, any surprises that occur would be the responsibility of the derived class developer, not the source class developer, much as is currently the case for non-static inheritance.
That being said, I'm not against the static constructor()
approach, though I can't say I like the idea of a class preventing me from properly extending it by some means other than a final
implementation.