what is Array.map(Function.call,Number)

前端 未结 4 1987
北荒
北荒 2021-02-06 01:36
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?

相关标签:
4条回答
  • 2021-02-06 01:49

    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.

    0 讨论(0)
  • 2021-02-06 01:56

    Some Background

    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.


    The Explanation

    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.

    0 讨论(0)
  • 2021-02-06 01:59

    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.

    0 讨论(0)
  • 2021-02-06 02:05

    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:

    • Callback function Function.call is invoked, passing args 1 (the current value), and 0 (the current index)
    • This callback is bound to the second arg, Number, before it executes, so it's like calling Function.call.bind(Number)(1, 0)
    • The first argument to Function.call says "bind to this context", but the explicit bind call above overrides this
    • What actually runs is Number(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 :)

    0 讨论(0)
提交回复
热议问题