Reputation: 89053
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)
?
Upvotes: 319
Views: 109612
Reputation: 2786
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));
Upvotes: 196
Reputation: 7600
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.
Upvotes: 154
Reputation: 573
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
Upvotes: 24
Reputation: 126
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.
Upvotes: 1
Reputation: 2855
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]
Upvotes: 0
Reputation: 2969
ES6 solution:
[...Array(10)]
Doesn't work on typescript (2.3), though
Upvotes: 36
Reputation: 167
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);
Upvotes: 10
Reputation: 2279
With ES6, you can do [...Array(10)].map((a, b) => a)
, quick and easy!
Upvotes: 122
Reputation: 14503
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.
Upvotes: 3
Reputation: 413712
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.
Upvotes: 6
Reputation: 323
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.
Upvotes: 4
Reputation: 10721
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.
Upvotes: 7
Reputation: 324517
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
.
Upvotes: 23
Reputation: 123453
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.
Upvotes: 35