Undefined values in Array(len) initializer

Consider:

var a = Array(3);
var b = [undefined,undefined,undefined];

What's the reason that a.map and b.map produce different results?

a.map(function(){  return 0;  });  //produces -> [undefined,undefined,undefined]
b.map(function(){  return 0;  });  //produces -> [0,0,0]

Answers:

Answer

The array constructor creates an array with the given length. It does not create the keys. Array.prototype.map's callback function is only executed for the elements in the list.
That is, all values which are associated with a key (integer) 0 ? i < length.

  • Array(3) has zero keys, so .map's callback is never triggered.
  • [void 0, void 0, void 0] has three keys, for which the callback function is executed.

    Array(3).hasOwnProperty(0);                 // false
    [void 0, void 0, void 0].hasOwnProperty(0); // true
    

The specification and its polyfill are mentioned at MDN. At line 47, if (k in O) { shows that non-existant keys are not treated by the callback function.

Answer

map only iterates existing properties, not empty indices.

Therefore, if you want it to work, you must first fill the array.

There are multiple ways to do that, for example:

  • .fill(), introduced in ES6

    console.log(new Array(3).fill().map(function(){ return 0; }));

  • Call concat with apply:

    var arr = [].concat.apply([], new Array(3));
    console.log(arr.map(function(){ return 0; }));

  • An old for loop.

    var arr = new Array(3);
    for(var i=0; i<arr.length; ++i) arr[i] = 1; /* whatever */
    console.log(arr.map(function(){ return 0; }));

  • Use some idea from Most efficient way to create a zero filled JavaScript array?

  • Etcetera.

Answer

a is an empty array that doesn't have elements, so map function produces empty array without elements (per specification, map produces results only if [[HasProperty]] is true.) b is an array of three elements, so map produces an array of three elements.

Answer

Constructed arrays are enumerable but empty

Array(len) creates an array and sets its length accordingly but only its length is "enumerable", not the values contained. So, you cannot map the array Array(100).map(/* nope */)— it's not a "real array" yet and it is actually empty despite having the correct length.

callback is invoked only for indexes of the array which have assigned values including undefined.,

The array does not contain any values; not even undefined

It is not called for missing elements of the array (that is, indexes that have never been set, which have been deleted or which have never been assigned a value).

To populate array you need to iterate it someway… E.g.: [...Array(100)] or Array.from(Array(100))

I imagine the purpose of this initialization is to optimize memory allocation… there isn't really anything in the array. MDN says "empty arrayLength objects" which might be misleading as trying to access any of the "empty items" just returns undefined… But we know they aren't really undefined since map fails, therefore we can confirm it is a truly empty array.

Constructor equivalent

This example does not seek to mirror specification but instead to illustrate why an array returned from Array cannot yet be iterated

function * (length) {
  const arr = [];
  Object.defineProperty(arr, 'length', { value: length });
  // Equivalent, but invokes assignment trap and mutates array:
  // arr.length = length;
  Object.defineProperty(arr, Symbol.iterator, {
    value() {
      let i = 0;
      return {
        next() {
          return {
            value: undefined, // (Not omitted for illustration)
            done: i++ == length
          };
        }
      }
    }
  })
  return arr;
}

It is worth pointing out that despite providing a undefined value in the generator value property, it does not recognize it as a value and so the array is empty.

Array(len) Specification

https://www.ecma-international.org/ecma-262/6.0/#sec-array-len

Array (len) This description applies if and only if the Array constructor is called with exactly one argument.

1) Let numberOfArgs be the number of arguments passed to this function call.

2) Assert: numberOfArgs = 1.

3) If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget.

4) Let proto be GetPrototypeFromConstructor(newTarget, "%ArrayPrototype%").

5) ReturnIfAbrupt(proto).

6) Let array be ArrayCreate(0, proto).

7) If Type(len) is not Number, then

a) Let defineStatus be CreateDataProperty(array, "0", len).

b) Assert: defineStatus is true.

c) Let intLen be 1.

8) Else, a) Let intLen be ToUint32(len). b) If intLen ? len, throw a RangeError exception.

9) Let setStatus be Set(array, "length", intLen, true).

10) Assert: setStatus is not an abrupt completion.

11) Return array.

Answer

From MDN:

callback is invoked only for indexes of the array which have assigned values; it is not invoked for indexes which have been deleted or which have never been assigned values.

For the array a, you've instantiated an array of length 3 but have not assigned any values. The map function finds no elements with assigned values, so it does not produce a new array.

For the array b, you've instantiated an array of 3 elements, each with the value undefined. The map function finds 3 elements with assigned values, and returns '0' as the new value for each of them in a new array.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.