On Dec 5, 2012, at 5:45 AM, Herby Vojčík wrote:

> 
> Allen Wirfs-Brock wrote:
>> super(...) is just shorthand for super.constructor(...) (when it
>> occurs in a constructor) so it is just a use of [[Call]]. No magic.
> 
> [[Call]]/[[Init]] aside.
> 
> After reading the spec, it is really so that super(...) in constructor is in 
> fact super.constructor(...), because
> - constructor has MethodName "constructor"
> - super(...) has generic super[MethodName](...) semantics.
> IOW, constructor is compiled as any other method with respect to semantics of 
> its code (it gains its [[Construct]] and .prototype magic later, but these 
> are external and do not touch the code).
> 
> It is bad for two reasons:

This is rehashing old territory, but let's go again...

> 
> 1. It will fail. There is lot of examples of code like:
> 
>  function Foo() { ... }
>  Foo.prototype = Object.create(Bar.prototype);
>  // or even (!!!) Foo.prototype = new Bar();
>  Foo.prototype.baz = function () {...};
>  ...
> 
> all over the web, in libraries etc.
> Some people add "Foo.prototype.constructor = Foo;" there, but some don't. Now 
> you see what happens if I try to use the new class:
> 
>  class Quux extends Foo {
>    constructor() { ....; super(...); ... }
>  }

If you are subclassing Foo,  then you better have some understanding that Foo 
is a well-formed "class-like" object.  Or, from another perspective, not adding 
a "constructor" property to Foo.prototype is explicating stating that it does 
not participating in super delegation of calls to "constructor".

In the past, there was minimal penalty to not maintaining the Foo.prototype to 
Foo link via the "constructor" property. In ES6, this changes.  Fortunately, 
there is really no reason to use the explicit class wiring pattern because a 
class definition does exactly the same thing for you.

In any case, you are defining the constructor that contains the super refernce. 
 If you are subclass something that is potentially malformed WRT  
prototype.constructor, then don't use super.  Instead, fall back to doing 
Foo.call(this,...)

> 
> super(...) call fails, because there is no Foo.prototype.constructor.

I't will get an inherited constructor property value, probably the one from 
Object.prototype

> 
> What's additional risk, the .constructor can be writable/configurable, so 
> something may be injected / it may be deleted. It is not what developers 
> assume. This relates to the:

We discussed making the .constructor property of the prototype object created 
by a class definition non-writable/non-configurable but in the end the 
consensus was to match what chapter 15 built-ins do, which is 
writable/configurable.  (But note that the prototype property of a class 
created constructor is non-writable/-non-configurable.  On a related note, I'm 
considering whether built-in @@create methods should be 
non-writable/non-configurable.  I can see arguments for both sides of that 
choice).

However, in general JS has very mutable objects and if you start mutating basic 
relationships,  things may behave oddly.  This relates to all uses of super, 
not just in constructors.

> 
> 2. It is not right (imho). Constructors are not methods (is there a movement 
> of making them into ones? I doubt, constructor methods are too in-grained in 
> the ECMAScript structure).

Well, in ES a method is defined to be a property with a function as its value.  
So, Quux.prototype.constructor is a method, by that definition.

> 
> Constructors are not methods, they are much more external to the class. They 
> _are_ the class, so what nearly every developer assumes when calling 
> super(...) in the constructor with extends Foo to call Foo. Not 
> Foo.prototype.constructor, which is way too brittle.

Note that constructors are invoked by [[Construct]], as if they, were a method. 
 Specifically the method is [[Call]]'ed with the this value set to the new 
instance.  So this.baz() within a constructor function is a method call on an 
instance.  So is this.constructor().

Since super(...) currently doesn't exist in JS, I don't think we can say what 
it means to nearly every developer other than that currently it means produce a 
syntax error.  In the future, what super(...) should mean, anywhere in ES code, 
is do a super invoke using the property name of the currently executing method. 
 For constructors, that name is "constructor".

> 
> It is the common practice now, Foo.apply(this, arguments) or Foo.call(this, 
> ...). The ES6 super(...) does something else.

It's common ES<6 practice, to do this in any method that needs to approximate 
super call behavior.  But it is not how ES6 super is defined.  One specific 
difference is that ES6 super correctly rebinds if __proto__ is used to change 
the [[Prototype]] of either the class prototype or the constructor object 
itself (which is relevant to class-side methods)

> 
> 
> Therefore, I propose super(...) has different semantics for constructor 
> methods*. It should do roughly (ctr being the constructor function):

Note you are saying that (only) in constructors, super() and 
super.constructor() have different semantics then they do in other 
methods(actually we are talking about unqualified and qualified super 
references in general) . Or do you only want to allow unqualified super in 
constructors and to disallow it in ordinary methods.  eg, the following would 
be illegal:

class Quux extends Foo {
   someMethod() {super()} // syntax error, unqualified super in a 
non-constructor method}
}

> 
> 1. Get [[Prototype]] of ctr into proto, fail if problem.
> 2. If proto is constructor function,
>  2.1. [[Call]]** proto with thisArg of this, and argument list equal to 
> super(...) argument list
>  2.2. return the completion of 2.1.
> 3. Otherwise, throw.***

think about potential __proto__ rewiring issues. 
> 
>> Allen
> 
> Herby
> 
> * That is, I'd like to see it in freeform constructor methods as well. Only 
> with super(...) shortened form. It's semantics is straightforward and prompts 
> replacing Bar.call(this, ...) with saner super(...); of course, one must 
> rewire Foo.__proto__ = Bar; to get this. May help the transition and 
> understanding that class has constructor inheritance as well, not only 
> prototype inheritance.

I'd be fine with allowing super in regular Function definitions.  Others 
aren't.  However, I would not want super in such declaration to make this sort 
of constructor usage assumption. A free standing function is just as likely to 
be plugged in as a "method" as it is as an constructor".  One advantage of such 
unqualified super references in function declarations (if we ever allow them) 
is that they are independent of any name in the source code.  When the function 
is super bound to an object (which is necessary to make super work at all) it 
picks up the actual property name used for that object. For example,

//hypothetical, doing things that ES6 currently does not allow:
function loggerMethod(...args) { console.log("called on ", this); return 
super(...args)}

Reflect.defineMethod(someInstance, "someMethod", loggerMethod);  //logs and 
calls super.someMethod
Reflect.defineMethod(someInstance, "anotherMethod",loggerMethod);  //logs and 
calls super.anotherMethod

> ** Or [[Init]] if adopted.
Unlikely, @@call + [[Call]] seem just fine.
> *** Alternatively, revert to super.constructor(...). I don't like it, though.

There are a lot of design trade-offs involved to incorporate super into ES. The 
issues you bring up was considered in arriving at the current design.  So far, 
your arguments haven't convinced me that we've made the wrong trade-offs. In 
fact, it has re-enforced (for me) that we have probably made the right ones.  
We could go through a process that reexamining it.  But consensus is fragile 
(within TC39 and anywhere else) and at some point we need to be content with 
what we have and move on to new issues.  In this light, I really don't want to 
get into another round of re-engineering super.

I think you may have a reasonable point about the integrity of the 
Foo.prototype.constructor relationship established by class definitions.  I 
originally proposed that the constructor property of such prototype objects 
should be non-writable/non-configurable.  However, at the July meeting we 
decided to make it be consistent with the existing chapter 15 classes which 
have writable/configurable constructor properties.  There is a reasonable 
argument based both upon internal consistency and flexibility for monkey 
patching behind that decision.  I would still be fine with making them 
non-writable/non-configurable.  But, without that, you still have the freedom 
to "freeze" Foo,prototype.constructor for your Foo function if you really are 
worried about the integrity of super references in the constructor.  If so you 
probably are also worried about the integrity of all of your methods, so you 
might as well freeze Foo.prototype.

Allen








_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to