307

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

user3840170
  • 26,597
  • 4
  • 30
  • 62
rampion
  • 87,131
  • 49
  • 199
  • 315
  • 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. – RussellUresti Mar 31 '11 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 – Hashbrown Jan 29 '20 at 00:37
  • when you use new Array(4) the resul tis not array with 4 "undefined" you got diffrent result - you got "(4) [empty × 4]" – yehonatan yehezkel May 31 '22 at 08:58

14 Answers14

190

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));
VLAZ
  • 26,331
  • 9
  • 49
  • 67
cstuncsik
  • 2,698
  • 2
  • 16
  • 20
  • 40
    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);` – Yann Eves Feb 11 '16 at 10:39
  • 1
    Similarly you can use `Array.from(Array(10))` –  Jul 15 '19 at 07:47
  • 1
    I would recommend @eden-landau 's answer, since it's a cleaner way of initializing an array – Sebastien H. Aug 06 '21 at 14:41
  • The answer is incorrect – Piliponful Feb 22 '22 at 11:57
  • Remember to use return when mapping to an object. :X `Array.apply(null, Array(10)).map(() => { return {}; });` – Trevor Karjanis May 19 '22 at 19:18
  • @TrevorKarjanis thanks for the hint but feels not relevant. Where do I need an object here? – cstuncsik May 24 '22 at 04:58
  • @cstuncsik You don't. You're returning an number `idx`. My comment is just to help anyone that followed your answer but needs to map to objects. – Trevor Karjanis May 24 '22 at 20:04
  • @TrevorKarjanis we're going way off topic here, but the crux of your statement is just how returned object literals work in a arrow functions: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions?retiredLocale=vi#returning_object_literals – Phil D. May 26 '22 at 02:42
  • @Phil D Yep! I've known that and still make the mistake every once in a while. – Trevor Karjanis May 26 '22 at 18:06
153

It appears that the first example

x = new Array(3);

Creates an array with a length of 3 but without any elements, so the indices [0], [1] and [2] is not created.

And the second creates an array with the 3 undefined objects, in this case the indices/properties them self are created but the objects they refer to are undefined.

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

As map runs on the list of indices/properties, not on the set length, so if no indices/properties is created, it will not run.

David Mårtensson
  • 7,550
  • 4
  • 31
  • 47
  • 107
    From [MDC](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/map) (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`. – Martijn Mar 31 '11 at 15:03
  • 2
    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]`. – Trevor Norris Sep 07 '12 at 22:20
  • 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 ;) – David Mårtensson Sep 17 '12 at 16:10
  • 8
    @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`. – squid314 Mar 19 '15 at 20:46
  • 3
    Talking about "undefined pointers" in JavaScript confuses the issue. The term you're looking for is ["elisions"](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-array-initializer). `x = new Array(3);` is equivalent to `x = [,,,];`, not `x = [undefined, undefined, undefined]`. – Matt Kantor Apr 14 '15 at 02:53
  • I never claimed that the two are equal, on the contrary, its that they are NOT equal I emphase, that in the first case the array has a size but no content and the mapping function cannot run but in the second there is objects even if they are undefined and map can run as long as you do not try to access the object. OP was confused by the fact that the two rendered the same output in firebug while not behaving the same in the map function. – David Mårtensson Apr 15 '15 at 06:50
  • I'm curious. Under the hood (in C++ land, etc.), what is the pointer pointing to if it is an "undefined pointer"? Does javascript allocate a special location in memory that represents "being undefined". – Magnus Apr 09 '20 at 15:53
  • The terminology is still wrong in this answer. The reason that `map` doesn’t execute its callback is that `new Array(3)` doesn’t have an index property up to its `length`. The point about `hasOwnProperty` is relevant: `map` will execute for every entry in `Object.keys(array)` (or `.values` or `.entries` etc.). If that’s empty, `map` will execute 0 times. This has nothing to do with “pointers”. The reason `new Array(3)[0]` is `undefined` is because that’s what non-existent properties produce. Good thing modern debugging tools make this difference more evident. “undefined object” is an oxymoron. – Sebastian Simon Nov 07 '21 at 11:34
118

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

Manuel Beaudru
  • 2,239
  • 3
  • 12
  • 8
34

ES6 solution:

[...Array(10)]

Doesn't work on typescript (2.3), though

Serge Intern
  • 2,669
  • 3
  • 22
  • 39
33

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.

Jonathan Lonowski
  • 121,453
  • 34
  • 200
  • 199
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.

Tim Down
  • 318,141
  • 75
  • 454
  • 536
23

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
Eden Landau
  • 553
  • 4
  • 12
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);
wenshin
  • 167
  • 1
  • 6
7

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.

helloandre
  • 10,541
  • 8
  • 47
  • 64
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.

Pointy
  • 405,095
  • 59
  • 585
  • 614
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.

Vezquex
  • 325
  • 3
  • 4
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.

Alex
  • 14,104
  • 11
  • 54
  • 77
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.

BPS Julien
  • 126
  • 2
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]
DJDaveMark
  • 2,669
  • 23
  • 35