Accesssing ES6 class constructor function

Don Griffin don at sencha.com
Thu Jan 5 21:30:36 UTC 2017


Hi James,

I too was surprised and disappointed with this restriction on constructors.

The enforcement seems pointless to me, because there are valid patterns
such as DI or multiple-inheritance where the JS run-time cannot know how a
framework is trying to accomplish the construction sequence.

This is really counter to the general philosophy of JS (imho) and more akin
to static languages like Java or C#... over there meta programming (MP)
happens in very different ways, but JS had previously made this task
simple... sadly this kind of thing starts to make MP harder at every step.

I don't hold much hope that this will be relaxed but it should be. :(

Best,
Don
--
Don Griffin
Director of Engineering
Sencha, Inc.
https://www.sencha.com/

On Thu, Jan 5, 2017 at 1: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.* The basic operation of the container
> might be like this
>
> ```js
> function createInstance(Cotr, args /* array */) {
>     function F() {
>        // this is dynamic in reality but simple example of injecting
> something...
>        this.logger = new Logger();
>
>        var instance = Cotr.apply(this, args)
>        return instance; // in case Cotr returns something
>     }
>     F.prototype = Cotr.prototype;
>     return new F();
> }
> ```
>
> So the Cotr can refer to "this.logger" in the constructor. I don't think
> there's a way to do this with dynamic class inheritance since you always
> have to call super() before you can assign any properties in a constructor.
>
> This isn't a dealkiller for the tool overall - I can constructor injection
> instead:
>
> ```js
> class Thing
>     static _inject = [Logger]
>     constructor(deps, ...args) {
>         // deps = { logger: Logger, dep2: Dep2, ... }
>         Object.assign(this, deps) // or however you want to make them
> available
>     }
> }
> ```
> This will work fine with the dynamic subclass pattern. it just was nice to
> have everything done by the framework and get rid of boilerplate, but this
> also has benefits of classes working without the DI container. :) I'm
> bringing this into an ES6 project for the first time so I can live with a
> different pattern.
>
>
> On Thu, Jan 5, 2017 at 1:55 PM, T.J. Crowder <tj.crowder at farsightsoftware.
> com> wrote:
>
>> 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.ht
>> ml#sec-reflect.construct
>>
>> [2]: https://github.com/caitp/TC39-Proposals/blob/master/tc3
>> 9-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
>>>
>>>
>>
>
> _______________________________________________
> 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/be2cec5f/attachment.html>


More information about the es-discuss mailing list