What does the following actually create?
I ask because it cannot be enumerated with Array#foreach
and it is represented (misleadingly?) in Chrome console as [undefined x 10]
.
const arr = new Array(10);
What does the following actually create?
I ask because it cannot be enumerated with Array#foreach
and it is represented (misleadingly?) in Chrome console as [undefined x 10]
.
const arr = new Array(10);
The Array
object has three different possible constructor actions:
new Array()
new Array(<multiple arguments)
new Array(<one argument>)
(placed as last because of its nature - see further)The behavior of the constructor depends of the arguments provided to that constructor. All those three actions uses ArrayCreate
during the constructor action. From the link:
The abstract operation ArrayCreate with argument length (a positive integer) and optional argument proto is used to specify the creation of new Array exotic objects.
This function accepts two arguments: len
(length) and proto
which is used to create an array object. (I won't go deeper here because it would be more complex). The len
argument is more important in this situation. When we are looking to the constructor actions individually, you have
new Array()
Here, an object is constructed by ArrayCreate(0, proto)
, thus an empty array with .length = 0
.
new Array(<multiple arguments>)
Here, an object is constructed by ArrayCreate(#of args, proto)
. If you provide 3 arguments, then you get an array with .length = 3
. This array is then populated with the provided arguments. Do you give 5 arguments? You get .length = 5
. So the .length
property depends of the number of the arguments.
new Array(<one argument>)
This one is quite different. It has two cases. At init, an array object is created with ArrayCreate(0, proto)
like if you're using new Array()
. Still we have an argument here to use.
If the argument is a non-numeric value, then the effect is same as point 2. This argument is being added to the constructed object (like a .push()
operation. So new Array('hello')
or new Array('10')
gives you an array object with .length = 1
Now, the thing is very different when you give a numeric argument new Array(10)
, which gives in Chrome [undefined x 10]
. Why? Because when the argument is numeric (and valid), this function is being called: Set(O,P,V,Throw)
. This "magic" function sets the property P
of object O
with the value V
. (The Throw
is a flag to indicate " throw error or not? " question).
If the argument is a length, set(<constructed array>, 'length', <argument>, true)
is being executed. So the .length
property returns the argument value while there is nothing being preserved in the memory. End result: you have an object with a fake .length
property... A side-effect of this is that using .push
adds an element to the 10th index (if .length
was 10). Now you have an array with 11 elements: 10 "empty" slots and an element that you've just pushed.
So what does the browsers do with an array with a "fake" .length
property? They cannot iterate it correctly. So they provide a failsafe way: display "has 10 elements but are empty" like chrome does with [undefined x 10]
or firefox: Array [ <10 empty slots>]
If you ask me why this is being standardized in the specs, then I would respond with "I don't know". It is one of the weirdest decision (among other ones that I have encountered) that is being standardized in ECMAScript.
The following two snippets are equivalent:
const arr = new Array(10)
and
const arr = [,,,,,,,,,,]
as can be demonstrated here:
const arr1 = new Array(10)
const arr2 = [,,,,,,,,,,]
let empty1 = true
for (let index = 0; empty1 && index < arr1.length; index++) {
if (arr1.hasOwnProperty(index)) {
empty1 = false
}
}
let empty2 = true
for (let index = 0; empty1 && index < arr2.length; index++) {
if (arr2.hasOwnProperty(index)) {
empty2 = false
}
}
console.log(empty1)
console.log(empty2)
This means that for all indices from [0, length - 1]
, they don't actually exist on the array because arr.hasOwnProperty(index)
is false
. This is distinguished from this example:
const arr = new Array(10).fill()
demonstrated here:
const arr = new Array(10).fill()
console.log(arr.every((_, index) => arr.hasOwnProperty(index)))
So if you want to initialize each index in a constructed array with undefined
, just call .fill()
without any arguments.
The Array
constructor when supplied with a single integer value creates an Array
instance with the length
own-property set to that value.
Host environments like browsers can choose to render this as they choose. V8 chooses [undefined x 10]
which is misleading because it implies there are ten instances of the predefined undefined
value in an ordered sequence.
This is not the case.
Array#foreach, Array#map use the enumerable integer indices AND the length
own-property value to enumerate the values of an array.
Because this specific Array
constructor syntax does not initialize any integer properties on the Array
object, they fail to enumerate Array
s initialized in this manner.
I can only presume that this syntax is soley present to enable code patterns that rely on the length
own-property of the Array
instance WITHOUT needing any keys or values to be initialized.