2

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);
Ben Aston
  • 53,718
  • 65
  • 205
  • 331
  • `console.log(arr);`. It will create an array with size 10 and undefined values. https://jsfiddle.net/o9f25Ljz/ – Edwin Jul 04 '17 at 14:11
  • No, i don't think it will. Please try it. – Ben Aston Jul 04 '17 at 14:11
  • What exactlyis the question? 10x `[undefined]` is exactly whats *supposed* to happen in that case. You never initailize any contents of the array. – Polygnome Jul 04 '17 at 14:12
  • 2
    The runtime distinguishes array elements that have been explicitly assigned values from those that haven't. – Pointy Jul 04 '17 at 14:12
  • @BenAston check my updated fiddle – Edwin Jul 04 '17 at 14:13
  • The representation in the fiddle is misleading IMO because of the behavior described in Pointy's comment. – Ben Aston Jul 04 '17 at 14:14
  • 2
    You can't enumerate, because array doesn't contain any element. You are confused because it looks like there are 10 `undefined` values, but it's not the case, there are nothing, *"holes"*. – dfsq Jul 04 '17 at 14:15
  • So what is the point of this syntax? Solely for the use case of code that wants to rely on the length property as a bound? – Ben Aston Jul 04 '17 at 14:16
  • Well the point is to create a new empty array. If you define , says, a new array of String or size 10. `new String[10]` You will have 10 pointer available for "filling" in your case you made a constant out of the same concept. of course you can't fill them. If you would remove the `const` it would be "fillable" – Nicolas Jul 04 '17 at 14:18
  • `const` is shallow, so you are not correct on that point. Arrays are dynamic in JS, so the size concept is fluid. – Ben Aston Jul 04 '17 at 14:21
  • 2
    `const` variables cannot be re-assigned. Object contents can, meanwhile, be changed. – Ben Aston Jul 04 '17 at 14:24

3 Answers3

3

The Array object has three different possible constructor actions:

  1. new Array()
  2. new Array(<multiple arguments)
  3. 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.

KarelG
  • 5,176
  • 4
  • 33
  • 49
1

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.

Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153
0

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 Arrays 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.

Ben Aston
  • 53,718
  • 65
  • 205
  • 331
  • from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach : `forEach() executes the provided callback once for each element present in the array in ascending order. It is not invoked for index properties that have been deleted or are uninitialized (i.e. on sparse arrays).` - if you want to add to your post – Edwin Jul 04 '17 at 14:41
  • You state that the constructor doesn't initializes any integer properties, but then why `arr[0]` outputs something? -> https://jsfiddle.net/o9f25Ljz/1/ – Edwin Jul 04 '17 at 14:55
  • The value of a not-present property is `undefined`. – Ben Aston Jul 04 '17 at 14:56
  • if I do `console.log(newArr[0]);` does output nothing(without initialization of newArr) - not `undefined` – Edwin Jul 04 '17 at 15:00