The following code:
let myArray = Array.apply(null, {length: 10}).map(Number.call, Number);
Creates the following Array:
[0, 1,
es6 simpliedfied version
let k = Array.from({ length: 5 }).map((currentElement, i) => i)
console.log(k)
// Output -[0, 1, 2, 3, 4]
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
Array.apply(null, {length: 10})
//> it outputs the array of undefined, with the length of ten, why: it takes null for execution because it does not need this for execution, besides this argument must be tepeof object, and as we know: typeof null === 'object', if you will place any other object instead of null, it will do the same thing, it matters only what is the length of the second argument( it tests the length of the second argument),
.map(Number.call, Number);
decompose(we know that Number constructor is Function.prototype(Number.proto), so):
.map(function(){
Function.prototype.call.apply(this, arguments)
}, Number)// the second arg is the this value that map function takes during each iteration
We also know that this is Number, cause every time it calls Number as this:
Function.prototype.call.apply(Number, arguments)
});//here we don't need second argument
Now we compose everything again: Function.prototype => Number,
call.apply(Number, arguments); => call(arguments[0], arguments[1])
reason: arguments is array-like object, we can still pass it to apply method, but we cannot to call, cause it takes arguments separated by comma.You cannot define arguments as an argument, so you need to indicate what it is looking for: index, and that is arguments[1], as this value it must take any object, so it take: null, or anything, but it must be present:
return Number.call(null, arguments[1]);
Here Number is an identity function:
function example(x){
return x;
}
so Number(e) == example(e); so on first iteration:
Number(0) //it takes 0 index:
return 0..
then:
Number(1)//cause it takes the index of the second element:
return 1..
Thanks for reading...
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.