Accesssing ES6 class constructor function

T.J. Crowder tj.crowder at farsightsoftware.com
Thu Jan 5 18:55:24 UTC 2017


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

I can't address your questions about "why" (I wasn't plugged into the
discussions around it), but addressing this:

> This has come into play lately for me, as an DI container we use that
> does exactly this doesn't work with ES6 classes (and as far as I can
> tell, there's no way to make it work, other than having devs no longer
> use class syntax).

Can you clarify what prevents it from being made to work? I'm probably
missing the point you're making there. For instance, this does some
brain-dead DI (injecting an argument in the constructor) by dynamically
extending the class:

```js
// The class we'll do DI on
class Original {
    constructor($foo) {
        this.foo = $foo;
    }
    run(num) {
        const result = this.foo.fooMethod(num);
        console.log(`num is ${num}, result is ${result}`);
    }
}

// Brain-dead di function
const di = (cls, Foo) => {
    const o = {
        [cls.name]: class extends cls {
            constructor(...args) {
                super(new Foo(), ...args);
            }
        }
    };
    return o[cls.name];
};

// Ues a class that's been DI'd
const use = Original => {
    new Original().run(42);
};

// Use it in dev
use(di(Original, class Foo {
    fooMethod(num) {
        return num * 2;
    }
}));

// Use it in production
use(di(Original, class Foo {
    fooMethod(num) {
        return num / 2;
    }
}));
```

That outputs

num is 42, result is 84

num is 42, result is 21

...because of the different injected `Foo`s. (This is obviously a
simplistic example.)

Separately, there are some tools you can use, such as
[`Reflect.construct`][1], but granted that does create an instance. For
instance, if for some reason you wanted to extend a class *without* using
`class`:

```js
class A {
    amethod() {
        console.log("amethod");
    }
}

function B() {
    const t = Reflect.construct(A, [], B);
    return t;
}
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

B.prototype.bmethod = function() {
    console.log("bmethod");
};

const b = new B();
b.amethod();                 // "amethod"
b.bmethod();                 // "bmethod"
console.log(b instanceof A); // true
console.log(b instanceof B); // true
```

Of course, that cheats a bit with that `return t;`. :-)

There are probably some tools that should be added to the list. For
instance, there's [this proposal][2] for `Reflect.isCallable` and
`Reflect.isConstructor`). And my `bmethod` above isn't really a method, so
it wouldn't be able to use `super`; in theory one could argue for a
`Reflect.makeMethod` (but use cases are limited, given `class` syntax). New
tools can be added if persuasive use cases come up (and people step forward
to define them and get a champion on board).

But circling back, I could be well wide of the mark above. If you can give
us more specifics about use cases that aren't supported, we can probably do
better helping with them.

[1]: http://www.ecma-international.org/ecma-262/7.0/index.html#sec-reflect.
construct

[2]:
https://github.com/caitp/TC39-Proposals/blob/master/tc39-reflect-isconstructor-iscallable.md

-- T.J.

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

> Hi - I am brand new to this list, I find myself here because of a
> confounding issue related to ES6 classes vs. traditional constructors.
> Forgive me if this is something that's been hashed out in times past. I
> looked around for discussion online and couldn't find anything more than
> the observation that the spec prohibits invoking it - not really any
> discussion. Probably a failing of google more than anything else, so if
> there's some discussion that I should read to catch up please point me
> there.
>
> Here's my issue. The ES6 spec prohibits invoking class constructors
> without "new". This makes such functions a special case, e.g.
>
> class Test() {}
>
> // typeof Test === 'function'  // yep
> // Test.prototype.constructor === Test // yep
>
> // Test() => nope ... TypeError: Class constructor Test cannot be invoked
> without 'new'
> // Test.call() ... nope
> // Test.apply() ... nope
>
> This has some interesting consequences. It means testing something for
> typeof "function" no longer guarantees it can be invoked without error.
> Also "function.toString()" can now return something that isn't actually a
> legal function definiton (since it returns the whole class as text). There
> seems to be no method, through various javascript reflection/invocation
> techniques or otherwise, to invoke a class constructor except by creating
> an instance of the class.
>
> For tool-builders the consequences of this are significant. It's no longer
> possible to create something that can extend/wrap/act on a prototype by
> intercepting it's construction process, as it was before with plain ES5
> constructors. So classes are fundamentally different than prototype
> contructors in how we can use them, far more than syntactic sugar. This has
> come into play lately for me, as an DI container we use that does exactly
> this doesn't work with ES6 classes (and as far as I can tell, there's no
> way to make it work, other than having devs no longer use class syntax).
>
> This seems a strange design decision. Even conventional OO languages like
> C# have the capability to reflect on classes and access the constructor
> directly as a function. It seems to fly in the face of the basic
> openness/dyanamic nature of JavaScript, and more signficantly, creates a
> kind of backward incompatibility since a function is no longer just a
> function.
>
> I'm wondering whether I'm missing some mechanism for legally accessing a
> class constructor as a function (other than parsing the output of
> toString() and eval!) -- and generally thoughts on this aspect of the ES6
> specification.
>
> Thank you!
>
>
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20170105/baaf2cd5/attachment-0001.html>


More information about the es-discuss mailing list