JavaScript “new Array(n)” and “Array.prototype.map” weirdness


I've observed this in Firefox-3.5.7/Firebug-1.5.3 and Firefox-3.6.16/Firebug-1.6.2

When I fire up Firebug:

var x = new Array(3)
console.log(x) 
// [undefined, undefined, undefined]

var y = [undefined, undefined, undefined]
console.log(y) 
// [undefined, undefined, undefined]

console.log( x.constructor == y.constructor) // true

console.log( 
  x.map(function() { return 0; })
)
// [undefined, undefined, undefined]

console.log(
  y.map(function() { return 0; })
)
// [0, 0, 0]

What's going on here? Is this a bug, or am I misunderstanding how to use new Array(3)?

It appears that the first example

x = new Array(3);

Creates an array with undefined pointers.

And the second creates an array with pointers to 3 undefined objects, in this case the pointers them self are NOT undefined, only the objects they point to.

y = [undefined, undefined, undefined]
// The following is not equivalent to the above, it's the same as new Array(3)
y = [,,,];

As map is run in the context of the objects in the array I believe the first map fails to run the function at all while the second manages to run.


I had a task that I only knew the length of the array and needed to transform the items. I wanted to do something like this:

let arr = new Array(10).map((val,idx) => idx);

To quickly create an array like this:

[0,1,2,3,4,5,6,7,8,9]

But it didn't work because: (see Jonathan Lonowski's answer a few answers above)

The solution could be to fill up the array items with any value (even with undefined) using Array.prototype.fill()

let arr = new Array(10).fill(undefined).map((val,idx) => idx);

console.log(new Array(10).fill(undefined).map((val, idx) => idx));

Update

Another solution could be:

let arr = Array.apply(null, Array(10)).map((val, idx) => idx);

console.log(Array.apply(null, Array(10)).map((val, idx) => idx));


With ES6, you can do [...Array(10)].map((a, b) => a) , quick and easy!


ES6 solution:

[...Array(10)]

Doesn't work on typescript (2.3), though


The arrays are different. The difference is that new Array(3) creates an array with a length of three but no properties, while [undefined, undefined, undefined] creates an array with a length of three and three properties called "0", "1" and "2", each with a value of undefined. You can see the difference using the in operator:

"0" in new Array(3); // false
"0" in [undefined, undefined, undefined]; // true

This stems from the slightly confusing fact that if you try to get the value of a non-existent property of any native object in JavaScript, it returns undefined (rather than throwing an error, as happens when you try to refer to a non-existent variable), which is the same as what you get if the property has previously been explictly set to undefined.


From the MDC page for map:

[...] callback is invoked only for indexes of the array which have assigned value; [...]

[undefined] actually applies the setter on the index(es) so that map will iterate, whereas new Array(1) just initializes the index(es) with a default value of undefined so map skips it.

I believe this is the same for all iteration methods.


In ECMAScript 6th edition specification.

new Array(3) only define property length and do not define index properties like {length: 3}. see https://www.ecma-international.org/ecma-262/6.0/index.html#sec-array-len Step 9.

[undefined, undefined, undefined] will define index properties and length property like {0: undefined, 1: undefined, 2: undefined, length: 3}. see https://www.ecma-international.org/ecma-262/6.0/index.html#sec-runtime-semantics-arrayaccumulation ElementList Step 5.

methods map, every, some, forEach, slice, reduce, reduceRight, filter of Array will check the index property by HasProperty internal method, so new Array(3).map(v => 1) will not invoke the callback.

for more detail, see https://www.ecma-international.org/ecma-262/6.0/index.html#sec-array.prototype.map

How to fix?

let a = new Array(3);
a.join('.').split('.').map(v => 1);

let a = new Array(3);
a.fill(1);

let a = new Array(3);
a.fill(undefined).map(v => 1);

let a = new Array(3);
[...a].map(v => 1);

I think the best way to explain this is to look at the way that Chrome handles it.

>>> x = new Array(3)
[]
>>> x.length
3

So what is actually happening is that new Array() is returning an empty array that has a length of 3, but no values. Therefore, when you run x.map on a technically empty array, there is nothing to be set.

Firefox just 'fills in' those empty slots with undefined even though it has no values.

I don't think this is explicitly a bug, just a poor way of representing what is going on. I suppose Chrome's is "more correct" because it shows that there isn't actually anything in the array.


Just ran into this. It sure would be convenient to be able to use Array(n).map.

Array(3) yields roughly {length: 3}

[undefined, undefined, undefined] creates the numbered properties:
{0: undefined, 1: undefined, 2: undefined, length: 3}.

The map() implementation only acts on defined properties.


Not a bug. That's how the Array constructor is defined to work.

From MDC:

When you specify a single numeric parameter with the Array constructor, you specify the initial length of the array. The following code creates an array of five elements:

var billingMethod = new Array(5);

The behavior of the Array constructor depends on whether the single parameter is a number.

The .map() method only includes in the iteration elements of the array that have explicitly had values assigned. Even an explicit assignment of undefined will cause a value to be considered eligible for inclusion in the iteration. That seems odd, but it's essentially the difference between an explicit undefined property on an object and a missing property:

var x = { }, y = { z: undefined };
if (x.z === y.z) // true

The object x does not have a property called "z", and the object y does. However, in both cases it appears that the "value" of the property is undefined. In an array, the situation is similar: the value of length does implicitly perform a value assignment to all the elements from zero through length - 1. The .map() function therefore won't do anything (won't call the callback) when called on an array newly constructed with the Array constructor and a numeric argument.


If you are doing this in order to easily fill up an array with values, can't use fill for browser support reasons and really don't want to do a for-loop, you can also do x = new Array(3).join(".").split(".").map(... which will give you an array of empty strings.

Quite ugly I have to say, but at least the problem and intention are quite clearly communicated.


Since the question is why, this has to do with how JS was designed.

There are 2 main reasons I can think of to explain this behavior:

  • Performance: Given x = 10000 and new Array(x) it is wise for the constructor to avoid looping from 0 to 10000 to fill the array with undefined values.

  • Implicitly "undefined": Give a = [undefined, undefined] and b = new Array(2), a[1] and b[1] will both return undefined, but a[8] and b[8] will also return undefined even if they're out of range.

Ultimately, the notation empty x 3 is a shortcut to avoid setting and displaying a long list of undefined values that are undefined anyway because they are not declared explicitly.

Note: Given array a = [0] and a[9] = 9, console.log(a) will return (10) [0, empty x 8, 9], filling the gap automatically by returning the difference between the two values declared explicitly.


Here's a simple utility method as a workaround:

Simple mapFor

function mapFor(toExclusive, callback) {
    callback = callback || function(){};
    var arr = [];
    for (var i = 0; i < toExclusive; i++) {
        arr.push(callback(i));
    }
    return arr;
};

var arr = mapFor(3, function(i){ return i; });
console.log(arr); // [0, 1, 2]
arr = mapFor(3);
console.log(arr); // [undefined, undefined, undefined]

Complete Example

Here's a more complete example (with sanity checks) which also allows specifying an optional starting index:

function mapFor() {
var from, toExclusive, callback;
if (arguments.length == 3) {
    from = arguments[0];
    toExclusive = arguments[1];
    callback = arguments[2];
} else if (arguments.length == 2) {
    if (typeof arguments[1] === 'function') {
        from = 0;
        toExclusive = arguments[0];
        callback = arguments[1];
    } else {
        from = arguments[0];
        toExclusive = arguments[1];
    }
} else if (arguments.length == 1) {
    from = 0;
    toExclusive = arguments[0];
}

callback = callback || function () {};

var arr = [];
for (; from < toExclusive; from++) {
    arr.push(callback(from));
}
return arr;
}

var arr = mapFor(1, 3, function (i) { return i; });
console.log(arr); // [1, 2]
arr = mapFor(1, 3);
console.log(arr); // [undefined, undefined]
arr = mapFor(3);
console.log(arr); // [undefined, undefined, undefined]

Counting Down

Manipulating the index passed to the callback allows counting backwards:

var count = 3;
var arr = arrayUtil.mapFor(count, function (i) {
    return count - 1 - i;
});
// arr = [2, 1, 0]

For reasons thoroughly explained in other answers, Array(n).map doesn't work. However, in ES2015 Array.from accepts a map function:

let array1 = Array.from(Array(5), (_, i) => i + 1)
console.log('array1', JSON.stringify(array1)) // 1,2,3,4,5

let array2 = Array.from({length: 5}, (_, i) => (i + 1) * 2)
console.log('array2', JSON.stringify(array2)) // 2,4,6,8,10