30

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]
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Engineer
  • 47,849
  • 12
  • 88
  • 91
  • 1
    Related: check out what happens if you set `b.length = 5;` before you run the `map`: `[0, 0, 0, undefined, undefined]` – apsillers Jun 29 '12 at 17:24
  • hi dear Engineer, is there any possibility to be in touch with you. Best regards. – RF1991 Jun 27 '22 at 18:09

5 Answers5

25

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.

Rob W
  • 341,306
  • 83
  • 791
  • 678
  • 1
    Actually, map should iterate not over the keys, but over the sequence of values 0..array.length-1 – panda-34 Jun 29 '12 at 17:32
  • @panda-34 Skipping keys between 0 and n is similar to walking over the numeric keys. But you're correct, strictly speaking, I should have said such ;) – Rob W Jun 29 '12 at 17:38
  • @RobW Can we assume, that `Array(3)` does not *really allocate memory* for storing `3` elements,which values are `undefined`, and it simply sets `length` property to `3`, just it? – Engineer Jun 29 '12 at 17:46
  • You didn't say "numeric", and non-numeric keys will be ignored by map and as such stripped from the result, which is not a hairsplitting distinction. And numeric is also not correct, all floating-point and negative-integer numeric keys (uh... they're all strings, really...) will also meet the fate of non-numerics. – panda-34 Jun 29 '12 at 17:50
  • 1
    @Engineer Yes. Verification is easy: Open Chrome's devtools (Ctrl+Shift+J) and the built-in task manager (Shift+Esc), and enter `window.x=Array(1e9);`. You don't see a significant increase in memory usage. `ArrayBuffer`, on the other hand: `x=new ArrayBuffer(1e9);` shows an increased memory consumption. – Rob W Jun 29 '12 at 18:05
6

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.

jbabey
  • 45,965
  • 12
  • 71
  • 94
5

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.

Community
  • 1
  • 1
Oriol
  • 274,082
  • 63
  • 437
  • 513
4

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.

Mohammad Kermani
  • 5,188
  • 7
  • 37
  • 61
panda-34
  • 4,089
  • 20
  • 25
2

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 intLenlen, throw a RangeError exception.

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

10) Assert: setStatus is not an abrupt completion.

11) Return array.

Bin Ury
  • 645
  • 7
  • 20