Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mapping Array in Javascript with sequential numbers

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?

like image 872
Jeremy Iglehart Avatar asked Jun 17 '16 18:06

Jeremy Iglehart


People also ask

How do you make an array of numbers in JavaScript?

var rank = new Array(1, 2, 3, 4); The Array parameter is a list of strings or integers. When you specify a single numeric parameter with the Array constructor, you specify the initial length of the array.

How do I map an array in JavaScript?

The syntax for the map() method is as follows: arr. map(function(element, index, array){ }, this); The callback function() is called on each array element, and the map() method always passes the current element , the index of the current element, and the whole array object to it.

Can you put numbers in an array JavaScript?

Use the for Loop to Sum an Array in a JavaScript Array We can use it to add all the numbers in an array and store it in a variable. Copy const array = [1, 2, 3, 4]; let sum = 0; for (let i = 0; i < array. length; i++) { sum += array[i]; } console.


3 Answers

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
like image 81
elclanrs Avatar answered Nov 06 '22 23:11

elclanrs


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.

like image 38
Blauelf Avatar answered Nov 06 '22 23:11

Blauelf


es6 simpliedfied version

let k = Array.from({ length: 5 }).map((currentElement, i) => i)

console.log(k)

// Output -[0, 1, 2, 3, 4]
like image 27
vijay Avatar answered Nov 07 '22 01:11

vijay