Accesssing ES6 class constructor function

T.J. Crowder tj.crowder at farsightsoftware.com
Fri Jan 6 10:31:00 UTC 2017


On Thu, Jan 5, 2017 at 7:21 PM, James Treworgy <jamietre at gmail.com> wrote:

> > Can you clarify what prevents it from being made to work?
>
> The fundamental feature difference (continuing to think about this!) is that
> with ES5 constructors, I can create an instance of something from an abitrary
> constructor provided to me, and inject properties that will be available to
> it *at contruction time.*

and

On Fri, Jan 6, 2017 at 3:49 AM, Don Griffin <don at sencha.com> wrote:

> ...the issue James mentioned with DI and I've hit with multiple-inheritance are
> the restriction on ".call()" and ".apply()" being used on constructor functions.

Thinking out loud:

Both James' DI case and the multiple-inheritance case could be
addressed with a Reflect utility function that allows you to provide a
hook triggered when `this` is being allocated for the base
constructor. This is basically `call` for constructors, but with a
hook rather than a *thisArg*. Just to have a name/concept for it:
`Reflect.new(targetConstructor, hook, ...args)`. For now let's assume
hook is a simple function that receives the freshly-allocated `this`
and can either augment it or return a replacement, but I'll circle
back to that later.

James' example:

```js
let o = Reflect.new(Ctor, thisObj => {
    thisObj.logger = new Logger();
});
```

Let's assume `Ctor` extends `Base`; it would work like this:

1. Call [EvaluateNew(`Ctor`, `args`, `hook`)][1], note the new third
argument. EvaluateNew passes the hook to Construct, which passes it to
[[Construct]].
2. Eventually during that [[Construct]], `Ctor` calls `super`, which
calls `Base`'s [[Construct]], also passing in the hook. (I don't know
yet how [`super(...)`][4] has access to the hook; I guess we'd have to
have it on the environment, like [[NewTarget]].)
3. We reach [Step 5][2] of `Base`'s [[Construct]] call. Since `Base`'s
*kind* is "base", we perform OrdinaryCreateFromConstructor, but then
pass the result through the hook. Since this particular hook doesn't
return an object, *thisArgument* is set to the result from
OrdinaryCreateFromConstructor as usual.
4. Construction completes as normal.

So even the base constructor sees James' `logger` property on `this`
by the time it has a `this`, because the hook gets a chance to augment
it before the base constructor code runs.

An MI example with `A`, `B`, and `C`, assuming they're all base constructors:

```js
let o = Reflect.new(C, () =>
    Reflect.new(B, () =>
        new A()
    )
);
// (Presumably do some mixing in of prototype properties...)
```

Execution is like before, but in this case when we reach Step 5 of
[[Construct]] for `B` and `C`, our hook returns an object, and so
*thisArgument* is set to that object rather than the one from
OrdinaryCreateFromConstructor.

I said I'd circle back on hook being a simple function: The MI example
above creates and throws away two objects it doesn't need to (the ones
created for `B` and `C` but then replaced by the hook). If that's a
concern, we can make hook an object with `allocate` and/or
`postAllocate` properties: `allocate` would provide `this` (MI),
`postAllocate` would just augment it (James' DI example). Or whatever.
That's jumping forward to design; we're still at the concept stage and
may well never reach design.

If needed for things like `Error`, constructors could have a flag
indicating that they cannot accept the hook (or at least, cannot
accept the hook providing a different `this`), causing a throw at Step
5 of [[Construct]].

Conceptually simple. Not necessarily simple in terms of impacts on
specification or implementations. In terms of the spec, we have at
least:

* Adding `Reflect.new` (or whatever it's called)
* Modifying EvaluateNew ([here][1])
* Modifying Construct ([here][3])
* Modifying [[Construct]] ([here][2])
* Keeping track of the hook somewhere such that evaluating
`super(...)` ([here][4]) can pass Construct the hook, possibly another
slot on environment records
* Possibly a flag slot or similar on functions like `Error`, if needed

I'm not competent to speak to impacts on implementations.

Which all sounds like a lot, but (modulo implementation complexity) I
don't think it really is, and I think it's basically what we'd need to
do to make `call`/`apply` work with constructors anyway (just passing
around a hook function/object rather than a *thisArg*), since we
wouldn't want `call`/`apply` to allow violating the "no `this` before
`super(...)`" rule by setting the `this` binding early.

-- T.J.


  [1]: http://www.ecma-international.org/ecma-262/7.0/index.html#sec-evaluatenew
  [2]: http://www.ecma-international.org/ecma-262/7.0/index.html#sec-ecmascript-function-objects-construct-argumentslist-newtarget
  [3]: http://www.ecma-international.org/ecma-262/7.0/index.html#sec-construct
  [4]: http://www.ecma-international.org/ecma-262/7.0/index.html#sec-super-keyword-runtime-semantics-evaluation


More information about the es-discuss mailing list