Accesssing ES6 class constructor function

Logan Smyth loganfsmyth at gmail.com
Thu Jan 5 21:41:06 UTC 2017


> The enforcement seems pointless to me

So we're all on the same page, this restriction exists because it means
`class` syntax can extend builtin types like `Map`, `Set`, `Array` and
`Error` and such, which cannot be extended (with correct functionality
anyway) in standard ES5. By delaying initialization of `this`, it allows
the constructor to drill all the way to the base class, which gets to
instantiate the correct type of object. To have `this` be initialized up
front, there would need to be some other mechanism in place to instantiate
the correct type of base object.

I feel your pain though, I ran into similar issues when updating our
codebase to ES6 classes, though I was able to work around it for our
specific usecase by having a base class handle hooking into things.

On Thu, Jan 5, 2017 at 1:30 PM, Don Griffin <don at sencha.com> wrote:

> 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
>>
>>
>
> _______________________________________________
> 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/c3618a09/attachment-0001.html>


More information about the es-discuss mailing list