问题
The following code:
let myArray = Array.apply(null, {length: 10}).map(Number.call, Number);
Creates the following Array:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
I just don't understand why. I can't find anything on the internet that explains this behavior. Does anyone know why this works the way it does? Perhaps a link to some documentation?
回答1:
Array.apply(null, {length: 10})
creates an array of length 10 with all elements being undefined
.
.map(Number.call, Number)
will invoke Number.call
for each element with the arguments (element, index, array)
and setting this
to Number
. The first argument to call will be taken as this
(not relevant here), and all the other arguments are passed as they are, with the first one being the index. And Number will now convert its first argument, index
, to a number (here: will return the index, as it is a number), and that's what map will write to its return array.
回答2:
Let's decompose the expression in two parts:
1) Let's discuss the first expression:
Array.apply(null, {length: 10})
In JavaScript the Array
constructor can take a parameter to create an array of a certain length, like:
Array(10) // makes an array of length 10
This array is a sparse array (an array which contains indexes with no elements). You can imagine we generated the following array:
[,,,,,,,,] // Array with 10 indexes, but no elements
You can think of an array in JavaScript as an object that has a length
property and numbered indexes. For example, the following is a valid representation of an array:
var arr = {length: 3, 0: 1, 1: 2, 2: 3} // this represents the array [1, 2, 3]
In JavaScript we call this object an "array-like object". You can iterate this object with a regular for
loop:
for (var i=0; i<arr.length; i++) {
console.log(arr[i]) // logs 1 .. 2 .. 3
}
But this object is not an instance of the Array
constructor:
arr instanceof Array // false
Fortunately, any array-like object can be converted to an array:
Array.prototype.slice.call({length: 3, 0: 1, 1: 2, 2: 3}) // [1, 2, 3]
All array methods are intentionally generic to allow this behavior, so you can easily loop with forEach
for example:
Array.prototype.forEach.call({length: 3, 0: 1, 1: 2, 2: 3}, function(x) {
console.log(x)
})
Now, back to the first expression:
Array.apply(null, {length: 10})
Decomposing the above expression knowing about array-like objects, we can see that is equivalent to:
Array(undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined);
In other words, we are creating an array of 10 elements with a value of undefined
(notice it is not sparse anymore)
2) Going into the second expression:
.map(Number.call, Number);
The first argument is a callback function to apply to each element in the array, the second argument is the this
value inside the callback.
Let's decompose this expression. First we can write out the callback function as an anonymous function:
Array.apply(null, {length: 10}).map(function() {
return Number.call.apply(this, arguments)
}, Number)
Then we realize that Number.call
is a shorthand for Function.prototype.call
:
Array.apply(null, {length: 10}).map(function() {
return Function.prototype.call.apply(this, arguments)
}, Number)
Next, we inline the this
value:
Array.apply(null, {length: 10}).map(function() {
return Function.prototype.call.apply(Number, arguments)
})
And finally we decompose the application of the function:
Array.apply(null, {length: 10}).map(function() {
return Number.call(arguments[0], arguments[1], arguments[2]) // map provides 3 arguments
})
As you can see, the first argument, which is the element, which is undefined
is the this
value of the call to Number
, which is to say we discard it. The second argument is the index, which is the value we care about, and the third argument is not needed because Number
only takes one argument, so this one is discarded as well.
In this case the Number
function is used as the identity function:
function id(x) {
return x
}
It is merely a function with one parameter that returns the argument passed into it. That's all we care about. Because index
is already a number, we get:
Number(index) === id(index)
Hope that helps further understanding.
Edit: To expand on the reason why Array(10)
won't work with iteration methods such as map
versus Array.apply(null, {length: 10})
we have to look at the implementation of map (scroll to the "Polyfill" heading).
The reason is because as I pointed out before, Array(10)
is a sparse array, it does not have any values in it, just a length. By looking at the implementation we can see what is happening:
// 8. Repeat, while k < len
while (k < len) {
var kValue, mappedValue;
// a. Let Pk be ToString(k).
// This is implicit for LHS operands of the in operator
// b. Let kPresent be the result of calling the HasProperty internal
// method of O with argument Pk.
// This step can be combined with c
// c. If kPresent is true, then
if (k in O) {
// i. Let kValue be the result of calling the Get internal
// method of O with argument Pk.
kValue = O[k];
You can see that in k in O
the in
operator checks for existence first, and the value does not exist; it is not undefined
, it is just not present. This is not the same as just doing property access like O[k]
where it would give you a value of undefined
if the property doesn't exist.
var o = {}
'p' in o // false
o.p // undefined
来源:https://stackoverflow.com/questions/37888470/mapping-array-in-javascript-with-sequential-numbers