Cristobal
Cristobal

Reputation: 35

Sparse arrays and reduce in JavaScript

One would think that in JavaScript: var array = [1,2,undefined,4]; is the same as:

var array = [1,2];
array.length = 3;
array.push(4);

but it's not. This code shows it:

var array1 = [1,2];
array1.length = 3;
array1.push(4);

var array2 = [1,2,undefined,4];

traverseArray(array1);
traverseArray(array2);

function traverseArray(array) {
  console.log("trying: " + array);
  console.log("reduce:");
  array.reduce(function(prev, current, index, array) {
    if(current === undefined) {
      console.log("Found undefined");
    }
    console.log(index+": " +current);
  }, undefined);

  console.log("for loop:")
  for(var i=0;i < array.length;i++) {
    var current = array[i];
    console.log(i+": " +current);
  }
  console.log();
}

Output:

trying: 1,2,,4
reduce:
0: 1
1: 2
3: 4
for loop:
0: 1
1: 2
2: undefined
3: 4

trying: 1,2,,4
reduce:
0: 1
1: 2
Found undefined
2: undefined
3: 4
for loop:
0: 1
1: 2
2: undefined
3: 4

Why is undefined in array1 not the same as undefined in array2 and why does the for loop act the same but reduce does not?

Upvotes: 2

Views: 547

Answers (2)

talemyn
talemyn

Reputation: 7950

apsillers nailed it in the comments . . . the difference is that in array2 you have actually assigned an undefined value to the 3rd element in the array (i.e., at index 2), where as, in array1, you have two elements initially, change the length property, and then add a third element in the forth position.

Here are the relevent sections from MDN that explains why the distinction is important:

  • From the page on Array.length:

When you extend an array by changing its length property, the number of actual elements does not increase; for example, if you set length to 3 when it is currently 2, the array still contains only 2 elements. Thus, the length property says nothing about the number of defined values in the array.

  • From the page on Array.push():

The push method relies on a length property to determine where to start inserting the given values.

The key here, is really that the length property is really, simple a property that is in no way inherantly tied to the contents of the array. It is simply because the various methods of Array also happen to maintain that property, as they "do their work", that I behaves as if it is.

So, as a result, in array2, the code is actually reporting back the undefined value that you assigned to array2[2], whereas, with array1, the code is interpreting the absence of a value at array1[2] as undefined.

Upvotes: 3

apsillers
apsillers

Reputation: 115940

array1 has three numerically-named properties: 0, 1, and 3.

array2 has four numerically-named properties: 0, 1, 2, and 3. The value of the property named 2 happens to be undefined.

When you ask an object for the value of a property it doesn't have, the result is undefined.

In the for loop, you ask each array for the values of its properties named 0, 1, 2, and 3. For array1, the property named 2 does not exist, so the property access produces undefined. For array2, the property does exist, but its value actually is undefined, so you get the same result.

On the other hand, reduce only operates on properties that actually exist. From the ECMAScript specification, this is how reduce loops over arrays, using a counter k:

  1. Repeat, while k < len
    • Let Pk be ToString(k).
    • Let kPresent be the result of calling the [[HasProperty]] internal method of O with argument Pk.
    • If kPresent is true, then... [use the value at index k for the reduce call]

So, we can see that an index is only used if passes a [[HasProperty]] check. array1 does not have a property named 2, so that index is skipped.

Upvotes: 4

Related Questions