dewd
dewd

Reputation: 4429

Javascript Array map "appears" to execute callback on missing elements

Map doesn't get executed on the following array.

Array(100).map(function(e,i){return i+1;});

console.log(Array(100).map(function(e, i) {
  return i + 1;
}));

I assume because all elements of the array are 'missing': https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map

However, map gets executed on all elements in the following:

Array.apply(null,Array(100)).map(function(e,i){return i+1;});

console.log(Array.apply(null, Array(100)).map(function(e, i) {
  return i + 1;
}));

How is it in the second example, the elements of the array change from 'missing' to 'undefined'? (at least I assume that is what is happening.)

Upvotes: 3

Views: 835

Answers (3)

Microfed
Microfed

Reputation: 2890

The main question is "Why Array(100) is an empty array with length equal 100 and Array.apply(null, Array(100)) is an array with 100 empty values?"

To answer that we need to go to the standard. Precisely, Standard ECMA-262, sections 22.1.1.2, 22.1.1.3, 19.2.3.1 and 7.3.17. I'll quote it here:

22.1.1.2 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
      Let defineStatus be CreateDataProperty(array, "0", len).
      Assert: defineStatus is true.
      Let intLen be 1.
8. Else,
      Let intLen be ToUint32(len).
      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.

22.1.1.3 Array (...items )

This description applies if and only if the Array constructor is called with at least two arguments.

When the Array function is called the following steps are taken:

1. Let numberOfArgs be the number of arguments passed to this function call.
2. Assert: numberOfArgs ≥ 2.
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(numberOfArgs, proto).
7. ReturnIfAbrupt(array).
8. Let k be 0.
9. Let items be a zero-origined List containing the argument items in order.
10. Repeat, while k < numberOfArgs
      Let Pk be ToString(k).
      Let itemK be items[k].
      Let defineStatus be CreateDataProperty(array, Pk, itemK).
      Assert: defineStatus is true.
      Increase k by 1.
11. Assert: the value of array’s length property is numberOfArgs.
12. Return array.

19.2.3.1 Function.prototype.apply ( thisArg, argArray )

When the apply method is called on an object func with arguments thisArg and argArray, the following steps are taken:

1. If IsCallable(func) is false, throw a TypeError exception.
2. If argArray is null or undefined, then
      Return Call(func, thisArg).
3. Let argList be CreateListFromArrayLike(argArray).
4. ReturnIfAbrupt(argList ).
5. Perform PrepareForTailCall().
6. Return Call(func, thisArg, argList).

7.3.17 CreateListFromArrayLike (obj [, elementTypes] )

The abstract operation CreateListFromArrayLike is used to create a List value whose elements are provided by the indexed properties of an array-like object, obj. The optional argument elementTypes is a List containing the names of ECMAScript Language Types that are allowed for element values of the List that is created. This abstract operation performs the following steps:

1. ReturnIfAbrupt(obj).
2. If elementTypes was not passed, let elementTypes be (Undefined, Null, Boolean, String, Symbol, Number, Object).
3. If Type(obj) is not Object, throw a TypeError exception.
4. Let len be ToLength(Get(obj, "length")).
5. ReturnIfAbrupt(len).
6. Let list be an empty List.
7. Let index be 0.
8. Repeat while index < len
      Let indexName be ToString(index).
      Let next be Get(obj, indexName).
      ReturnIfAbrupt(next).
      If Type(next) is not an element of elementTypes, throw a TypeError exception.
      Append next as the last element of list.
      Set index to index + 1.
9. Return list.

As you can see, there is plenty of work going here. In short, when Array(100) executes it produces an array-like object with property length equal to 100, but there is no objects inside that Array. But, if you'll try to get any, it will return undefined for any indexes. Because of that, when CreateListFromArrayLike executes, it gets all 100 undefined values from array-like object provided and returns actual List with 100 undefined values in it, which goes to the Array(...items) function.

Upvotes: 0

lex82
lex82

Reputation: 11317

In your call

Array.apply(null,Array(100)).map(function(e,i){return i+1;});

the Array function is called with 100 arguments (in fact, the length of Array(100) is 100). But when accessing the arguments, all of them are undefined.

If you would call some arbitrary function func(a, b) like this:

func.apply(null, Array(2))

The parameters a and b will be undefined and the length of arguments will be 2.

map() iterates over the elements in the array but there are actually no elements! However, the array has length 100. This is weird but this is the way arrays behave in JS. If you use the array as an argument list for a function (via .apply()), the arguments are accessed and become undefined. The original array does not change, but accessing an index in the empty array yields undefined.

Upvotes: 3

Oriol
Oriol

Reputation: 288590

Let's see how MDN defines "missing":

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

Internally, this is done with [[HasProperty]]. You can use the in operator to check [[HasProperty]] manually.

And now see the difference:

var arr = Array(100);
'0' in arr; // false
var arr = Array.apply(null, Array(100));
'0' in arr; // true

Upvotes: 2

Related Questions