Allen Wirfs-Brock (2014-02-19T02:57:24.000Z)
On Feb 18, 2014, at 4:59 PM, Jason Orendorff wrote:

> On Tue, Feb 18, 2014 at 3:34 PM, Allen Wirfs-Brock
> <allen at wirfs-brock.com> wrote:
>>> Of course adding an extra internal slot to all DOM objects is not necessarily all that great either...  Though maybe we have to do that no matter what to prevent double-initialization.
>> 
>> You don't necessary need a new slot to to use as an initialized flag. I most cases you can probably get away with using a "not yet initialized" token in some existing slot.
> 
> Mmm. Well, sure, I think we can avoid bloating every Array to
> accomodate [[ArrayInitialisationState]], by optimizing for the case
> where it is true. But when you've got information, ultimately it has
> to be stored, somehow, somewhere.
> 
> More to the point, this adds an axis to the configuration space of
> Array objects. That’s inelegant. It makes new kinds of bugs possible.
> Now that we're talking about extending the pattern to the whole DOM,
> it sounds worse than it did a few months ago. :-P

I believe that Array was the only ES6 built-in where I had to add such an initialization state internal slot.  All others already had slots available that could do double duty for this flag. I suspect the same is true for most DOM objects. 

Such initialization checks have three purposes;
1)    It prevents methods that depend upon internal state of built-ins from being applied to initialized instances.  Array instances and methods actually doesn't have any such dependencies and none of its methods check for initialization.
2)    Prevents potentially destructive double initialization.  Generally, not an issue for Arrays but see use case 3 below
3)    Used to distinguish calls to constructors for initialization (including super calls to the constructor) from "calling the constructor as a function".

The last one is the odd case.

We need to be able to do do:
     class SubArray extends Array {
           constructor (...args) {
                 this.foo = whatever;
                 super(...args);
      }};
or even more explicitly
     class SubArray extends Array {
           constructor (...args) {
                 this.foo = whatever;
                 Array.apply(this, args);
      }};

(for most purposes these do the same thing and in particularly the super call and the Array.apply both "call Array as a function" in exactly the same way)

But ES<6 also says that calling Array as a function allocates and initializes a new Array object.   So the challenge was how to extend Array (and other existing builtin or host object constructors) to continue to have their legacy "called as a function" behavior and still work as a super callable initializer.  There were a few specific use cases that had to be considered (these all might exist in existing JS code):

1)
    Array(1,2,3,4)  //should create a new Array and initialize it with 4 elements
this is easy, the this value is undefined.  So, the spec says that if the this value passed to the constructor is not an object, it must allocate a new instance and then initializes it.

2)
    var myNamespace = {Array: Array};
    myNamespace.Array(1,2,3,4)  //should create a new Array  and initialize it with 4 elements
The this value passed to Array will be the myNamespace object.  We don't want to initialize it as an Array, but instead we need to allocate a new array instance.  So, the spec. says that if the this value  passed to the constructor is not an object that is not an Array , it must allocate a new instance and then initializes it. Array objects are identified (in the spec)  by the present of the [[ArrayInitializationState]] internal slot but could alternatively be identified using some sort of an "exotic array object" brand or any other implementation specific branding of array instances.

3)
   Array.prototype.somethingNew = Array;
   var arr = new Array(5,6,7,8);
   arr.somethingNew(1,2,3,4)  //should create a new Array  and initialize it with 4 elements
The this value pass to Array is the Array arr, so it is already branded  as an Array but we don't what to reinitialize it and over-write its elements with the wrong values. So, the spec. says that if the this value passed to the constructor is branded as an Array and its [[ArrayInitializationState]] is true, the constructor must allocate a new instance and then initialize it.  This works because [[ArrayInitializationState]] didn't exist in ES<6.  In ES6 the only way to get a handle on an uninitialized array is by directly calling Array[Symbol.create] or by intercepting such an uninitialized instance in a subclass constructor prior to a super call to the Array constructor. 

If this and other constructs didn't have "new is optional" constructor behavior, must of this would be unnecessary. That's why I generally recommend against people trying to define ES6 style class constructors that work without using new.  It's too easy to screw up the above cases and not worth the effort for new code.  

Overall, I think this is actually a pretty elegant solution, given the legacy compatibility constraints we have to navigate moving to classes  subclassable built-ins in ES6.

Regarding the DOM, it would clearly be simpler if Gecko (or Trident) DOM constructors required use of new.  If you think optional new could be deprecated as non-standard over a reasonable period, that might be a good thing to do prior to making DOM constructors subclassable. But even if that doesn't seem possible, the above pattern is something can consistently applied to all the DOM constructors.

Allen
domenic at domenicdenicola.com (2014-02-24T21:14:58.257Z)
I believe that Array was the only ES6 built-in where I had to add such an initialization state internal slot.  All others already had slots available that could do double duty for this flag. I suspect the same is true for most DOM objects. 

Such initialization checks have three purposes;
1. It prevents methods that depend upon internal state of built-ins from being applied to initialized instances.  Array instances and methods actually doesn't have any such dependencies and none of its methods check for initialization.
2. Prevents potentially destructive double initialization.  Generally, not an issue for Arrays but see use case 3 below
3. Used to distinguish calls to constructors for initialization (including super calls to the constructor) from "calling the constructor as a function".

The last one is the odd case.

We need to be able to do do:

```js
class SubArray extends Array {
    constructor (...args) {
        this.foo = whatever;
        super(...args);
    }
}
```

or even more explicitly

```js
class SubArray extends Array {
    constructor (...args) {
        this.foo = whatever;
        Array.apply(this, args);
    }
}
```

(for most purposes these do the same thing and in particularly the super call and the Array.apply both "call Array as a function" in exactly the same way)

But ES<6 also says that calling Array as a function allocates and initializes a new Array object.   So the challenge was how to extend Array (and other existing builtin or host object constructors) to continue to have their legacy "called as a function" behavior and still work as a super callable initializer.  There were a few specific use cases that had to be considered (these all might exist in existing JS code):

1. ```js
   Array(1,2,3,4)  //should create a new Array and initialize it with 4 elements
   ```
   this is easy, the this value is undefined.  So, the spec says that if the this value passed to the constructor is not an object, it must allocate a new instance and then initializes it.

2. ```js
   var myNamespace = {Array: Array};
   myNamespace.Array(1,2,3,4)  //should create a new Array  and initialize it with 4 elements
   ```
   The this value passed to Array will be the myNamespace object.  We don't want to initialize it as an Array, but instead we need to allocate a new array instance.  So, the spec. says that if the this value  passed to the constructor is not an object that is not an Array , it must allocate a new instance and then initializes it. Array objects are identified (in the spec)  by the present of the [[ArrayInitializationState]] internal slot but could alternatively be identified using some sort of an "exotic array object" brand or any other implementation specific branding of array instances.

3. ```js
   Array.prototype.somethingNew = Array;
   var arr = new Array(5,6,7,8);
   arr.somethingNew(1,2,3,4)  //should create a new Array  and initialize it with 4 elements
   ```
   The this value pass to Array is the Array arr, so it is already branded  as an Array but we don't what to reinitialize it and over-write its elements with the wrong values. So, the spec. says that if the this value passed to the constructor is branded as an Array and its [[ArrayInitializationState]] is true, the constructor must allocate a new instance and then initialize it.  This works because [[ArrayInitializationState]] didn't exist in ES<6.  In ES6 the only way to get a handle on an uninitialized array is by directly calling Array[Symbol.create] or by intercepting such an uninitialized instance in a subclass constructor prior to a super call to the Array constructor. 

If this and other constructs didn't have "new is optional" constructor behavior, must of this would be unnecessary. That's why I generally recommend against people trying to define ES6 style class constructors that work without using new.  It's too easy to screw up the above cases and not worth the effort for new code.  

Overall, I think this is actually a pretty elegant solution, given the legacy compatibility constraints we have to navigate moving to classes  subclassable built-ins in ES6.

Regarding the DOM, it would clearly be simpler if Gecko (or Trident) DOM constructors required use of new.  If you think optional new could be deprecated as non-standard over a reasonable period, that might be a good thing to do prior to making DOM constructors subclassable. But even if that doesn't seem possible, the above pattern is something can consistently applied to all the DOM constructors.