324

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

3
  • I don't get the same results you see from the array literal notation. I still get undefined instead of 0. I only get the 0 result if I set something like var y = x.map(function(){return 0; });, and I get this for both the new Array() method and the array literal. I tested in Firefox 4 and Chrome. Commented Mar 31, 2011 at 14:50
  • also busted in Chrome, this might be defined in the language, although it makes no sense so I really hope it isnt Commented Jan 29, 2020 at 0:37
  • when you use new Array(4) the resul tis not array with 4 "undefined" you got diffrent result - you got "(4) [empty × 4]" Commented May 31, 2022 at 8:58

14 Answers 14

199

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)

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

Sign up to request clarification or add additional context in comments.

9 Comments

worth noting you don't need to state undefined in the .fill() method, simplifying the code very slightly to let arr = new Array(10).fill().map((val,idx) => idx);
Similarly you can use Array.from(Array(10))
I would recommend @eden-landau 's answer, since it's a cleaner way of initializing an array
The answer is incorrect
Remember to use return when mapping to an object. :X Array.apply(null, Array(10)).map(() => { return {}; });
|
154

It appears that the first example

x = new Array(3);
// This is the same as
x = [,,,];

Creates an array with a length of 3 but without any items. The indices (slots) [0], [1] and [2] are not created. See sparse arrays docs.

And the second example:

y = [undefined, undefined, undefined];

creates an array with 3 items (slots), each with the value undefined. In this case the slots ([0], [1] and [2]) are created and the items they refer to are undefined.

The map array function runs on the list of indices/properties, not on the array length, so if no indices/properties are created, it skip them

8 Comments

From MDC (emphasis mine): "map calls a provided callback function once for each element in an array, in order, and constructs a new array from the results. 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." In this case, x's values have not explicitly assigned values, whereas y's were assigned, even if it was the value undefined.
So is it a JavaScript failing that it's impossible to check whether it's an undefined pointer, or a pointer to undefined? I mean (new Array(1))[0] === [undefined][0].
Well, an array of undefined is different from an array of pointers to undefined objects. An array of undefines would be like an array of null values, [null, null, null] while an array of pointers to undefined would be like [343423, 343424, 343425] poining to null and null and null. The second solutions have real pointers pointing to memory addresses while the first do not point anywhere. If that is a failing of JS is probably a matter o discussion, but not here ;)
@TrevNorris, you can easily test that with hasOwnProperty unless hasOwnProperty itself has a bug: (new Array(1)).hasOwnProperty(0) === false and [undefined].hasOwnProperty(0) === true. In fact, you can do the exact same with in: 0 in [undefined] === true and 0 in new Array(0) === false.
Talking about "undefined pointers" in JavaScript confuses the issue. The term you're looking for is "elisions". x = new Array(3); is equivalent to x = [,,,];, not x = [undefined, undefined, undefined].
|
124

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

4 Comments

Pre-ES6 you can use new Array(10).fill(). Same result as [...Array(10)]
With large arrays the spread syntax creates issues so it's better to avoid
or [...Array(10).keys()]
I know how it works, but please add some explanation, for those who don't
36

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.

Comments

36

ES6 solution:

[...Array(10)]

Doesn't work on typescript (2.3), though

1 Comment

Array(10).fill("").map( ... is what worked for me with Typescript 2.9
25

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

1 Comment

This is the cleanest answer to me.
23

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.

Comments

10

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

Comments

8

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.

Comments

6

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.

3 Comments

It's defined to be broken? It's designed to produce an array of three elements that will always be undefined for ever?
Yes, that's correct, except for the "forever" part. You can subsequently assign values to the elements.
That's why you should use x = [] instead of x = new Array()
4

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.

Comments

3

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.

Comments

1

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.

Comments

0

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]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.