Why can't I create a random array using _.map(

2019-06-25 04:08发布

问题:

I want to make an array of random numbers between 0 and 1, so I tried:

var randList = _.map(new Array(5), Math.random);

But instead of getting the list of random elements that I expected, I got:

console.log(JSON.stringify(randList));
"[null,null,null,null,null]"

Why did I get an Array of null instead of random numbers?

回答1:

The Array(length) constructor creates a sparse array. That is, the length of the array is set to the specified parameter, but the elements of the array are not populated. As a result, the map method does not iterate over these 'unpopulated' elements. For example, the following snippet does not produce any output on the console:

var arr = new Array(5);
for(var x in arr) console.log(x);

Note, however, that initializing the array like this will produce the expected result:

console.log(_.map([0,0,0,0,0], Math.random));

Of course, this doesn't scale. If you want the number of elements in the array to be variable, I would recommend you use the times method instead:

var randList = _(5).times(Math.random);

or

var randList = _.times(5, Math.random);


回答2:

Actually, you get an array of undefined - and function passed into map isn't fired even once. That happens because array created with new Array(Number) constructor is a bit weird - even though its length is set up correctly, it doesn't actually have 0, 1 etc. properties:

var arr = Array(5);
console.log(arr.length); // 5
console.log('0' in arr); // false

That's why, even though plain old for (var i = 0; i < arr.length; i++) will work for iterating over this array, for (var i in arr) won't work:

for (var i in arr) {
   console.log(i); // won't be called even once!
}

And that's, I suppose, is more-o-less similar to what happens with Array.map (at which _.map is mapped):

var newarr = arr.map(function(el) {
   console.log(el); // nope, still nothing
});
console.log(newarr); // [undefined x 5]

So what's the right way of creating such an array? Here it is:

var rightArr = Array.apply(0, Array(5)); // ah, the pure magic of apply!
// and here goes an array of randoms:
var randArr = rightArr.map(Math.random);

... but of course using _.times is the way to go in this case. )



回答3:

Just want to add some test cases to raina77ow's excellent answer.

Indeed, new Array(5) doesn't alloc 5 properties in the Array object.

It's doesn't matter whether you use underscore or plain javascript Array.prototype.map (in decent browser of course).

Here are test cases in node.js

new Array(2) just returns an empty object (no keys) with 2 (kind of fake) placeholders

> var a = new Array(2);
> a;
[ ,  ]
> a.length;
2
> Object.keys(a);
[]

the map result is also an empty object (no keys) with 2 placeholders

> var result = a.map(Math.random);
> result;
[ ,  ]
> result.length;
2
> Object.keys(result);
[]

Why the result is an array object of length 2, even when there is nothing done?

Read the Array.prototype.map polyfill implmentation https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map

The map result was created as var res = new Array(source_array_length);

In addition, map works with a filled Array even when values are undefined

> [undefined, undefined].map(Math.random);
[ 0.6572469582315534, 0.6343784828204662 ]

Ok, let's also test sparse array, length is 2 but it only contains one key '1'.

> var a = []; a[1] = 'somthing';
> a;
[ , 'somthing' ]
> a.length
2
> Object.keys(a);
[ '1' ]

The map result returns an array of length 2, but only one key '1', only one Math.random is fired!

> var result = a.map(Math.random);
undefined
> result.length
2
> result
[ , 0.8090629687067121 ]  // only one Math.random fired.
> Object.keys(result);
[ '1' ]