Why are object initializer methods not usable as constructors?

T.J. Crowder tj.crowder at farsightsoftware.com
Tue May 16 05:48:38 UTC 2017


Methods not being constructors also makes them lighter-weight: They have no
`prototype` property and associated object.

This isn't limiting developer creativity or freedom. If you want to create
an object with constructor functions, you have at least two ways to do that:

1. `class`:

```js
let ctors = {
    Foo: class { },
    Bar: class { }
};
```

2. `function`:

```js
let ctors = {
    Foo: function { },
    Bar: function { }
};
```

> but most JS authors who don't know about these pesky new JavaScript
> language internals might be inclined to write:

Then the library author warns them not to do that in the documentation
(maybe even a really clear error message in the non-min build), or they
figure it out really quickly when they do and it doesn't work. :-)

-- T.J. Crowder


On Tue, May 16, 2017 at 2:16 AM, Jordan Harband <ljharb at gmail.com> wrote:

> You seem to be suggesting that ES6 should be making it easier for you to
> reimplement a core ES6 language feature. If you want `class`, can you not
> use `class`? That's what users who have access to ES6+ environments are
> likely to do anyways.
>
> It's also worth noting that someone could do `constructor: () => {}` or
> `constructor: function *() {}` and `new`ing them would fail the same way.
>
> On Mon, May 15, 2017 at 5:25 PM, /#!/JoePea <joe at trusktr.io> wrote:
>
>> > Because they're methods, not functions. The distinction between the
>> > two was merely semantic in ES5, but now it's mechanical, due to
>> > super(); constructing something intended as a method would make
>> > super() behave in confusing and unintuitive ways, so methods just
>> > don't have a constructor any more.
>>
>> You are correct for app authors, but wrong for library authors. A
>> library author may easily like to make an object that contains ES5
>> constructors, and writing them like
>>
>> ```
>> let ctors = {
>>   Foo() {},
>>   Bar() {},
>> }
>> ```
>>
>> is simply simple.
>>
>> For example, suppose a library author releases a `Class` function for
>> defining classes, it could be used like this:
>>
>> ```
>> const Animal = Class({
>>   constructor: function() {
>>     console.log('new Animal')
>>   }
>> })
>> ```
>>
>> but most JS authors who don't know about these pesky new JavaScript
>> language internals might be inclined to write:
>>
>>
>> ```
>> const Animal = Class({
>>   constructor() {
>>     console.log('new Animal')
>>   }
>> })
>> ```
>>
>> If the `Class` implementation returns that "constructor", then when
>> the user does `new Animal` they will get an unexpected error, and that
>> is not ideal at all for a dynamic language like JavaScript.
>>
>> What's even stranger is that the `Class` implementation can wrap the
>> concise method with a [[Construct]]able function and call the concise
>> method with `.call` or `.apply` and it will work! But that is simply
>> just messy and ugly.
>>
>> So why prevent it from working only sometimes? It would be much better
>> for it to just work all the time, and make restrictions only when
>> `super` is present in the function body. In the above example, the
>> `constructor() {}` concise method does not use the keyword `super`, so
>> treating it like `constructor: function constructor() {}` would be
>> much more ideal.
>>
>> We shouldn't limit developer creativity for reasons that don't exist
>> (I haven't heard of any compelling reasons so far).
>>
>> The new language features cause failures in unexpected ways, and I
>> really don't think the language should be designed in this
>> less-intuitive way.
>>
>> JavaScript pre-ES6 has a dynamic nature, but ES6 and newer features
>> are less-so in that tradition.
>>
>> > Making this one niche use-case ("I want to define several
>> constructor-only classes inline in an object initializer") require a few
>> characters less is not a sufficiently worthwhile benefit for the cost.
>>
>> Actually, no, I want to allow end-users of my library to pass in
>> objects containing methods and properties, and I don't want the result
>> to fail in unexpected ways, and I also don't want to write ugly and
>> hacky code to make it work.
>>
>> > Just type the few extra characters (exactly what you would have typed
>> in ES5, so it's not even a new imposition), and you'll be fine.
>>
>> Like I said, it won't be me typing these things, it will be end users.
>> Yes I can disguise the problem, but if I for example were implementing
>> a `Class` tool, I wouldn't like to wrap their non-constructable
>> methods in a proxy function just to make it work not only because it
>> is ugly, but because it will show things in the console that are more
>> difficult to debug.
>>
>> For example, have you ever looked at classes made with Backbone? They
>> are not so nice to inspect because Backbone wraps constructors and
>> prototypes like an onion.
>>
>> This language "feature" of concise methods that makes them not
>> constructable forces library authors to write ugly code who's output
>> is harder to inspect and debug by end developers.
>> /#!/JoePea
>>
>>
>> On Sat, Jul 30, 2016 at 3:04 AM, /#!/JoePea <joe at trusktr.io> wrote:
>> >>  The distinction between the two was merely semantic in ES5, but now
>> it's
>> >> mechanical, due to super(); constructing something intended as a method
>> >> would make super() behave in confusing and unintuitive ways, so
>> methods just
>> >> don't have a constructor any more.
>> >
>> > So, if `super` were dynamic, then it would be no problem.
>> >
>> >> It's been explained to you already in previous threads why super() is
>> >> designed the way it is, and how a dynamic super() would add
>> significant cost
>> >> to some situations
>> >
>> > Those "some situations" haven't been listed yet (or I don't know where
>> they
>> > are listed). Do you know any? As far as I can tell, a dynamic super
>> would
>> > perform just fine:
>> >
>> > - For constructor calls, `HomeObject` can just the `.prototype`
>> property of
>> > the function when `new.target` is the same as the function being
>> > constructed. That's simple.
>> > - If `new.target` is not the current constructed function, then the
>> current
>> > function was found on the prototype chain of
>> > `Object.getPrototypeOf(new.target)` (i.e. tje `.constructor` property
>> was
>> > found on some `HomeObject` in the prototype chain) and then that
>> function is
>> > called with the found `HomeObject`. This seems like a simple addition
>> to the
>> > property lookup algorithm.
>> > - Functions called as methods simply have `HomeObject` passed as the
>> > prototype (HomeObject) where they were found. This seems like a simple
>> > addition to the property lookup algorithm.
>> > - What else?
>> >
>> > Based on those ideas from my limited knowledge on thetopic, a dynamic
>> > `super` doesn't seem to "costly".
>> >
>> > Suppose I write
>> >
>> > ```js
>> > obj.foo()
>> > ```
>> >
>> > Then, in ES5, there is already going to be a property lookup algorithm
>> to
>> > find `foo` on the prototype chain of `obj`. Therefore, when the
>> > property`foo` is found on a prototype (a HomeObject), it doesn't seem
>> like
>> > all that much extra cost to simply pass that found object by reference
>> to
>> > the `foo` method call, since we already found it. That's not very
>> costly.
>> >
>> > I may be using the word "HomeObject" wrong, but I think you get what I
>> mean.
>> >
>> >
>> > /#!/JoePea
>> >
>> > On Thu, Jul 28, 2016 at 9:11 AM, Tab Atkins Jr. <jackalmage at gmail.com>
>> > wrote:
>> >>
>> >> On Wed, Jul 27, 2016 at 11:44 AM, /#!/JoePea <joe at trusktr.io> wrote:
>> >> > What's the good reason that object-initializer methods can't be
>> >> > constructors
>> >> > though? I mean, I get that "that's what spec says", but what's the
>> >> > actual
>> >> > good reason?
>> >>
>> >> Because they're methods, not functions. The distinction between the
>> >> two was merely semantic in ES5, but now it's mechanical, due to
>> >> super(); constructing something intended as a method would make
>> >> super() behave in confusing and unintuitive ways, so methods just
>> >> don't have a constructor any more.
>> >>
>> >> There are several very similar ways you can write your example that do
>> >> achieve what you want. As Allen said, you can just use the non-concise
>> >> syntax, explicitly typing "function" (or better, "class") for each of
>> >> the values.  This is only a few characters more and achieves exactly
>> >> what you want.
>> >>
>> >> It's been explained to you already in previous threads why super() is
>> >> designed the way it is, and how a dynamic super() would add
>> >> significant cost to some situations.  Making this one niche use-case
>> >> ("I want to define several constructor-only classes inline in an
>> >> object initializer") require a few characters less is not a
>> >> sufficiently worthwhile benefit for the cost.  Just type the few extra
>> >> characters (exactly what you would have typed in ES5, so it's not even
>> >> a new imposition), and you'll be fine.
>> >>
>> >> ~TJ
>> >
>> >
>> _______________________________________________
>> 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/20170516/9ed1913a/attachment.html>


More information about the es-discuss mailing list