Allen Wirfs-Brock (2014-02-19T02:57:24.000Z)
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.