19

I was trying to generate some random data using map. To my surprise, I couldn't figure out why this code isn't working.

Consider the following snippet which works as expected:

const empty = [undefined, undefined];
const rand = empty.map(item => Math.random());

Output: [0.4774752874308936, 0.8482276976659398]

I tried to simplify a bit and do the following

const rand = Array(2).map(item => Math.random())

Output: [undefined × 2]

I cannot understand why this is happening. Clearly, both arrays generated by Array(n) and [] are typical arrays and have all prototype methods.

Array(2) instanceof Array
true

[undefined, undefined] instanceof Array
true

Array.isArray(Array(2))
true

Array.isArray([undefined, undefined])
true

Can someone point out where I am going wrong here?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Karthik Iyengar
  • 584
  • 1
  • 4
  • 14
  • 3
    btw - you can work around it by using `fill` - `Array(2).fill(null).map(item => Math.random())` – Ori Drori Dec 02 '16 at 09:44
  • 1
    There's also `const rand = [...Array(2)].map(item => Math.random());` ;-) – T.J. Crowder Dec 02 '16 at 09:54
  • 2
    Love that one :) Or even shorter: `[...Array(2)].map(Math.random);` – Me.Name Dec 02 '16 at 09:55
  • @Me.Name: Good point! [The definition](http://www.ecma-international.org/ecma-262/7.0/index.html#sec-math.random) of `Math.random` makes no use of `this`. – T.J. Crowder Dec 02 '16 at 09:57
  • 4
    Or `Array.from({ length: 2 }, Math.random);` – Ori Drori Dec 02 '16 at 10:00
  • @OriDrori: ***That's*** what I was trying to remember. I'd remembered it as `[...{length:2}]`, but of course, plain objects have no iterator. It was the `Array.from` part I'd forgotten. – T.J. Crowder Dec 02 '16 at 10:01
  • Oh, I like SO questions like this. @OriDrori idea is neat, extending to do other stuff like Array of squares -> `Array.from({length:11},(i,ix) => ix*ix)` – Keith Dec 02 '16 at 10:11
  • See also [Why does `.forEach` work on dense arrays but not on sparse arrays?](http://stackoverflow.com/q/28974006/1048572) and [Why can't I create a random array using `_.map(new Array(n), Math.random)`?](http://stackoverflow.com/q/22738287/1048572) – Bergi Dec 02 '16 at 16:27

4 Answers4

27

Array(2) gives you an empty array with a length of 2. JavaScript arrays are inherently sparse (they can have holes in them, because they aren't really arrays at all¹), and that's what Array(2) gives you. It looks like this:

+−−−−−−−−−−−−−−+
|    (array)   |
+−−−−−−−−−−−−−−+
| length: 2    |
+−−−−−−−−−−−−−−+

whereas your [undefined, undefined] array looks like this:

+−−−−−−−−−−−−−−+
|    (array)   |
+−−−−−−−−−−−−−−+
| length: 2    |
| 0: undefined |
| 1: undefined |
+−−−−−−−−−−−−−−+

map, forEach, and most (but not all) of the related methods on Array.prototype only loop through the actual entries of a sparse array. Since the array returned by Array(2) doesn't have any actual entries, your callback is never being called.

ES2015 added Array#fill (and it can be shimmed), which you can use to fill an array:

const rand = Array(2).fill().map(Math.random)
console.log(rand);

(Note that as Me.Name pointed out we don't need item => Math.random, we can call Math.random directly; it doesn't use this or its arguments (spec).)

There's also this trick for creating a filled array with undefineds in it:

const empty = [...Array(2)]:

...which you could apply like this if you didn't want fill:

const rand = [...Array(2)].map(Math.random);
console.log(rand);

Ori Drori points out that we can do

Array.from({length: 2}, Math.random);

e.g.:

const rand = Array.from({length: 2}, Math.random);
console.log(rand);

And Nina Scholz adds the classic, ES5-compatible:

Array.apply(null, {length: 2}).map(Math.random);

var rand = Array.apply(null, {length: 2}).map(Math.random);
console.log(rand);

¹ (That's a post on my anemic little blog)

Community
  • 1
  • 1
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
4

You could look into the specs of Array#map:

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

You could use Array.apply for getting an iterabel array with map.

console.log(Array.apply(null, { length: 2 }).map(Math.random));
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • 2
    *"You could look into the specs of `Array#map`"* MDN isn't "the specs." It's secondary documentation. (*Very good* secondary documentation.) [The spec](http://www.ecma-international.org/ecma-262/7.0/index.html#sec-array.prototype.map) is the spec. – T.J. Crowder Dec 02 '16 at 10:08
4

That's because you assume that Array(2) is interpreted by the JavaScript engine somewhat like this: "Create an array object with two entries initialized to a default value, say 0 or undefined".

Actually, the JavaScript engine creates a new object and sets its length property to 2. It does not have any real content.

Check this very simple example:

var arr = new Array(2);
console.log(arr);

If you run it and check the console you're going to see something like this:

Array[2] => {
    length: 2,
    __proto__: Array[0]
}

No contents, no placeholders. Just an empty array with length initialized to 2.

That is why your arrow function never gets called.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
dimlucas
  • 5,040
  • 7
  • 37
  • 54
-1

You are creating an array initialized with size of 2. But there isn´t any element in these positions. then you do
[undefined, undefined].map(item => Math.random())

You have an array with two undefined elements in it.

kimy82
  • 4,069
  • 1
  • 22
  • 25
  • This means nothing, the initial value is irrelevant, it's the Math.random() that would be the value applied. You should've also realized this from the fact that the first example worked. The .map method is essentially an assignment for every element in an array. – DibsyJr Dec 02 '16 at 10:03
  • 1
    I do not understand. What I am saying is that doing Array(2) you are not filling the array with elements. Just defining the lenght. And doing [undefined, undefined] you are filling the array with elements so map function will iterate the elements – kimy82 Dec 02 '16 at 10:09
  • Riiiight, well from what you said I got the impression you were implying that they are one in the same, i.e. that initializing an array of size 2 creates [undefined, undefined], which would expect the same result as the first example. Might have been why someone voted you down, but I don't know (it wasn't me). – DibsyJr Dec 02 '16 at 10:13