The following code is taken from Jon Resig's book Secrets of JavaScript Ninja to explain how to use closures to implement partial application of functions. However I have issues understanding the intent of the variable arg. Why is it required and how does it simplify the problem in hand of pre-filling some arguments to a function? What can be a possible application of this partial function?

    Function.prototype.partial = function() {
          var fn = this, args = Array.prototype.slice.call(arguments);
          return function() {
            var arg = 0;   // How does this do the currying
            for (var i = 0; i < args.length && arg < arguments.length; i++) {
              if (args[i] === undefined) {
                args[i] = arguments[arg++]; //This line is where the confusion is
              }
            }
            return fn.apply(this, args);
          };
        };

Edit: I am confused because args and arguments are necessarily the same here since after the call args = Array.prototype.slice.call(arguments); args is an true array object that holds all the information contained in arguments. So if something is undefined in args, how can we possibly have something inside arguments?

有帮助吗?

解决方案

Ok, I'm trying to explain it piece by piece:

Function.prototype.partial = function() {
      var fn = this, args = Array.prototype.slice.call(arguments);
      return function() {
        var arg = 0;
        for (var i = 0; i < args.length && arg < arguments.length; i++) {
          if (args[i] === undefined) {
            args[i] = arguments[arg++]; //This line is where the confusion is
          }
        }
        return fn.apply(this, args);
      };
 };

First line:

var fn = this, args = Array.prototype.slice.call(arguments);

This stores the value of this and the value of arguments in two variables, because both values would get overriden in the following function block:

return function() {
    //inside here arguments will be whatever is passed to this returned function later.
};

The for loop:

var arg = 0;
for (var i = 0; i < args.length && arg < arguments.length; i++) {
}

It will basically loop through all arguments that were passed to the partial function and exits earlier if arg >= arguments.length.

if (args[i] === undefined) {
    args[i] = arguments[arg++]; //This line is where the confusion is
}

So, if an argument of the args array is undefined, we replace it with the next argument in the arguments array. When every arguments are replaced, the original function gets called with the merged arguments array:

 return fn.apply(this, args);

Here is how it would work in practice:

 function xy(arg1, arg2) {
     console.log(arg1 + " / " + arg2);
 }

 var p1 = xy.partial("foo", undefined);

 p1("bar") //"foo / bar"

 var p2 = xy.partial(undefined, "bar");

 p2("foo") //"foo / bar"

 var p3 = xy.partial("foo");

 p3("bar") //"foo / undefined" --> because you have to activly pass "undefined" otherwise the arguments array is too short

And last but not least, how this code workes in detail with the example p1 = xy.partial("foo", undefined); :

//lets call xy.partial("foo", undefined);
Function.prototype.partial = function() {
      //fn = xy
      //args = ["foo", undefined]
      var fn = this, args = Array.prototype.slice.call(arguments);

      //return function that is assigned to p1
      //lets call p1("bar")
      return function() {
        //arguments = ["bar"]
        var arg = 0;
        //for (var i = 0; i < 2 && arg < 1; i++)
        for (var i = 0; i < args.length && arg < arguments.length; i++) {
          //first iteration:
          //args[0] === "foo" -> nothing happend
          //second iteration:
          //args[1] === undefined -> args[1] = arguments[0] (= "bar"); arg++;
          if (args[i] === undefined) {
            args[i] = arguments[arg++]; //This line is where the confusion is
          }
        }
        //at this point: args = ["foo", "bar"];
        //now just call the function with the merged array
        return fn.apply(this, args);
      };
 };

其他提示

Partial function helps you to pre fill the function arguments that is always needed for the function execution. This helps is reducing the overheads in many ways.

The example above helps in filling undefined arguments later.

The arg is a private variable to keep track of the undefined arguments and fill the undefined arguments in sequence.

args[i] = arguments[arg++];

The above line filling the undefined arguments those were left undefined when using partial.

Example:-

function a(b,c){ console.log(b,c); }

//pre filling the second argument with numeric value 1. But first argument is still undefined. This will return a new function.

var g = a.partial(undefined, 1);

//Now when you actually invoke the function and pass it the parameters this will fill the arguments left undefined previously i.e. "b" . In this case the var arg will fill the previous args array position those were undefined i.e. args[0] was undefined. So var arg will read the passed arguments in sequence and fill the previously undefined arguments for you.

g("Hi I was undefined before");

If don't want to args gets permanently change then update the partial implementation as below.

Function.prototype.partial = function() {
                var fn = this, args = Array.prototype.slice.call(arguments);
                return function() {
                  var arg = 0, g;//g will hold the clone of args
                  g = args.slice(0);//clone the args array and use that instead of original args.
                  for (var i = 0; i < g.length && arg < arguments.length; i++) {
                    if (g[i] === undefined) {
                      g[i] = arguments[arg++];
                    }
                  }

                  return fn.apply(this, g);
                };
            };

function fn(a, b){  }
var fn = f.partial(undefined, 3), x = fn(2), y= fn(4);//Now this will work

I think this is best explained in words. At least, I'll give it a go :

The outer function gives rise to the outer arguments (once) and the inner function gives rise to the inner arguments (once or more times).

Meanwhile args is an "accumulator" in which parameters of the first (outer) call and those of all subsequent (inner) calls are progressively stored. The line :

args[i] = arguments[arg++];

simply does the accumulating, on a next-to-arrive basis.

At each inner call fn.apply(this, args) may or may not execute successfully. fn will be written in full knowledge that it will curried. Typically,

  • for an incomplete set of parameters fn will return say undefined (or null or false or -1)
  • for a complete set of parameters fn will return a meaningful result.
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top