Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScript Map: Why does this not work? [duplicate]

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?

like image 911
Karthik Iyengar Avatar asked Dec 02 '16 09:12

Karthik Iyengar


3 Answers

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)

like image 182
7 revs Avatar answered Oct 29 '22 15:10

7 revs


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));
like image 32
Nina Scholz Avatar answered Oct 29 '22 15:10

Nina Scholz


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.

like image 24
dimlucas Avatar answered Oct 29 '22 14:10

dimlucas