Mapping Array in Javascript with sequential numbers

限于喜欢 提交于 2019-12-21 02:22:14

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!