Accesssing ES6 class constructor function

T.J. Crowder tj.crowder at farsightsoftware.com
Fri Jan 6 13:34:19 UTC 2017


Note: Related discussion of calling class constructors here:
https://esdiscuss.org/topic/determine-if-a-value-is-callable-constructible

On Fri, Jan 6, 2017 at 12:11 PM, James Treworgy <jamietre at gmail.com> wrote:

> T.J. Thanks for the very thoughtful analysis. But I keep coming back to
> this:
>
>> since we
>> wouldn't want `call`/`apply` to allow violating the "no `this` before
>> `super(...)`" rule by setting the `this` binding early.
>
> Why?
>
> To me the best solution is also the simplest - just let people do this.

On first blush, a couple of reasons, but that doesn't mean they aren't
addressable:

1. I think the supposed simplicity is illusory. If you get into the
mechanics of actually "just" allowing people to do it with
`call`/`apply`, as I said earlier, I think it looks very like
`Reflect.new` but with less flexbility and with the "`this` before
`super()`" problem. Do you have a proposal for how to actually do it
that doesn't have similar complexity? You'd have to modify `call` and
`apply`, you'd still have to modify how Construct and [[Construct]]
work (at the very least). Maybe you get some savings by allowing a
`this` binding before `super` (you might avoid the hook slot on the
environment), but my instinct is the savings are small change. But I
haven't gone through the same level of analysis on it as I did with
`Reflect.new`, so I'm open to being proved wrong on that.

 Granted *using* them would be simpler **if** you didn't care whether
you're calling a constructor vs. non-constructor (e.g., you "just" use
`call`/`apply`). But is it really the case that there's a lot of code
that doesn't need to care? Calls and construction are very different
things.

 But a win for overloading `call`/`apply` is, of course, that ES5
constructor code can subclass an ES2015+ class constructor without
branching. Existing ES5 code could be fed an ES2015+ class constructor
and (probably) Just Work™. But I don't know that it's a common use
case outside DI frameworks, which are relatively few and thus
upgradeable.

2. Having `call`/`apply` not work the same way a function call does
(modulo their actual purpose) is a significant departure from how
they've worked since they were added to JavaScript originally. Now
every explanation of them must not only say "They call the function
setting a specific value for `this`" but also "**and** they let you
bypass the restriction preventing calling constructors as functions."
Okay, so the counter-argument could be that allowing them to call a
constructor as a function is a by-product of their main purpose
(setting `this`), but to me it's both different and surprising.

3. Allowing `this` prior to `super()` in a subclass constructor
depending on how it's called makes it impossible for lint tools and
such to flag that up early as an error. So they can't support the
majority case where that's going to be an error at runtime.

Perhaps all of those can be addressed or dismissed. I don't want to
give the impression of being married to anything (not that my opinion
is particularly important anyway). But my gut says overloading
`call`/`apply` (thus allowing early `this` binding) isn't markedly
simpler, but is more problematic.

Side note: In my outline of `Reflect.new`, I should have flagged up
that we'd really want at least the `Reflect.isConstructor` part of
[this proposal](https://github.com/caitp/TC39-Proposals/blob/master/tc39-reflect-isconstructor-iscallable.md)
to be in place. DI systems would need to know what they were dealing
with. But (to me, anyway) the `Reflect.isConstructor` part of that
proposal is non-controversial (`Reflect.isCallable` has an open
question about what it should say for class constructors -- how
relevant!).

I feel like I've done a lot of talking on this; I'm going to hang back
and just listen for a while. :-)

-- T.J.


More information about the es-discuss mailing list