var array1 = [1, 4, 9, 16];
map1=array1.map(Function.call,Number);
Why the output of map1 is [0,1,2,3], what this map function is doing?
Array.map function creates a new Array, with the results of calling the provided Function (Function.call) on every element in the Array that calls Array.map.
e.g.
// Store an initial Array of String values
var stringArray = ['Matt','Long','JavaScript'];
// Invoke Function for each value of Array
mapResult = stringArray.map((currentValue) => {
// Concatenate and return
return currentValue + ' Software';
});
// Log map result
console.log(mapResult);
You are passing in Number as the 'this' value to use, when executing callback for each value in the Array. In the result Array, each element is the result of calling the given Function on each value in the Array.
The Function.call method calls a Function with a given 'this' value and the arguments provided.
Array.prototype.map
takes two arguments: the first is a function which is called with each item in the array, the second, rarely-used argument is a this
value which the first argument is called with. For example:
[1,2,3].map(function() { return this.x; }, {x: 3}) // returns [3,3,3]
Function.prototype.call
is a function that is on all functions: (Function
is itself a function, so Function.call
is Function.prototype.call
), which allows the function to be called with a specific this
and a specific set of arguments (passed individually, not as an array in the case of Function.prototype.apply
). For example:
function someFunction(y) {
return this + y;
}
someFunction.call(2, 3) // Returns 5, since this=2 and y=3
But there's an extra detail in the real example, because it's not doing someFunction.call
, it's doing Function.call
: what's going on is Function.prototype.call
is using this
to determine what function gets called: if you rebind this
, a different function gets called. You can rebind this
with, well... Function.prototype.call
.
So this is equivalent to the previous example:
function someFunction(y) {
return this + y;
}
Function.call.call(someFunction, 2, 3)
The second call to .call
rebinds the first one, so that it invokes someFunction
instead of Function
, and passes it the arguments 2 and 3: it boils down to exactly someFunction.call(2, 3)
, which we've already seen.
Going back to the real example so we can put things together, we have
[3,6,9].map(Function.call, Number)
The second argument does binding, just like .call
, so this is equivalent to:
[3,6,9].map((...args) => Function.call.call(Number, ...args))
which as we just saw, is equivalent to the more straight forward:
[3,6,9].map((...args) => Number.call(...args))
So, now, what are ...args
? For each call, they're the item in the array, the position in the array, and the whole array. So really this expands to:
[
Number.call(3, 0, [3,6,9]),
Number.call(6, 1, [3,6,9]),
Number.call(9, 2, [3,6,9])
]
Now remember, Number.call
's first argument is this
, while the other two arguments are provided to the function being called, Number
. As far as I know, Number
doesn't use this
, so it essentially just throws the first argument away, so it's:
[
Number(0, [3,6,9]),
Number(1, [3,6,9]),
Number(2, [3,6,9])
]
Number
, only takes a single argument though, which it casts to a Number. That's trivial, though since it's argument already is a number. So that's how you get [1,2,3]
. It's just the indexes, being needlessly cast to numbers, with a lot of function indirection.
Array.prototype.map calls the function provided for each member of the array, and returns a new array of their return values.
In this case, the provided function is Function.call.
The second argument to Array.prototype.map
specifies the context in which the provided function should run.
In this case, the context is Number.
A naive implementation of Array.prototype.map
could look something along the lines of:
function map(callback, thisArg) {
const ret = []
for (let index = 0; index < this.length; index++) {
const value = this[index]
ret.push(callback.call(thisArg, value, index, this))
}
return ret
}
Now, this particular case uses a lot of indirection so it's not easy to see why passing Function.call
and Number
should return [0, 1, 2, 3]
, so lets walk through that part.
When the callback
(Function.call
) is called in the context of the thisArg
(Number
) the code will execute something along the lines of:
(Function.call).call((Number), (1), (0), ([1, 4, 9, 16]))
// ^-- callback ^-- value ^-- array
// ^-- thisArg ^-- index
Evaluating Function.call.call(...)
is a bit of a mind-bender, but it amounts to calling the call
function in the context of the first parameter, which should be a function. In our case, it is. It's the Number
function.
We can then simplify this statement to look like:
Number.call(1, 0, [1, 4, 9, 16])
The Number
function converts the first parameter to a number. Other parameters are ignored, along with the functions context. This means the entire expression can be simplified to:
Number(0)
where 0
was the index
.
This is why the return value is [0, 1, 2, 3]
, as these are the indices from the original array.
It should go without saying that the original code sample is not something that should ever be used in day-to-day programming. The entire system would have been simpler to use:
[1, 4, 9, 16].map((value, index) => index)
and even then it should be noted that the original values are being thrown out uselessly. Such code samples are useful only in the academic sense of exploring behaviors of a particular language, or when you want to intentionally confuse your friends as part of a coding challenge.
Read up on the usage of Array#map here
Input args are map(callback, this)
In your example, you're supplying the function Function.call
as the mapper, which is the constructor for Function. Function is a basic way to say "run this function", and the this
you're binding is to give it the first arg.
Where it gets interesting, is the callback you give it takes args (currentValue, index)
. By passing through Function.call and binding it, you're actually forcing it to drop the first arg. This is a clever (read: difficult to understand) way of getting your "Number" function to run on the index.
e.g. try [1, 4, 9, 16].map(Function.call, String)
, and you'll see the same thing but parsed as strings ["1", "4", "9", "16"]
.
Let's step through what happens on the first iteration to get some more clarity:
Function.call
is invoked, passing args 1
(the current value), and 0
(the current index)Number
, before it executes, so it's like calling Function.call.bind(Number)(1, 0)
Function.call
says "bind to this context", but the explicit bind
call above overrides thisNumber(0)
, which is of course 0
, the index.Nowadays, I think most people would simply use an arrow function with a first arg they can ignore, e.g. array1.map((_, i) => Number(i))
(or better yet, Number.parseInt
, if you're trying to get an integer).
While this whole thing is very cool to read as a software developer, I wouldn't recommend this map(Function.call)
pattern in practice! If you've come across it in your own code, at least add a comment so the next dev doesn't need to come to SO :)