JavaScript咖喱

时间:2011-03-03 03:26:00

标签: javascript

我是JavaScript的新手,试图理解本教程关于来自Oreilly JavaScript Cookbook的curry。

有人可以用简单的语言逐步详细解释这个程序。请务必解释在程序的倒数第二行传递的“null”参数。如果您能提供帮助,请提前感谢您。

function curry(fn, scope) {
    scope = scope || window;
    var args = [];
    for (var i = 2, len = arguments.length; i < len; ++i) {
        args.push(arguments[i]);
    }
    return function() {
        var args2 = [];
        for (var i = 0; i < arguments.length; i++) {
            args2.push(arguments[i]);
        }
        var argstotal = args.concat(args2);
        return fn.apply(scope, argstotal);
    };
}

function diffPoint(x1, y1, x2, y2) {
    return [Math.abs(x2 - x1), Math.abs(y2 - y1)];
}

var diffOrigin = curry(diffPoint, null, 3.0, 4.0);
var newPt = diffOrigin(6.42, 8.0); //produces array with 3

5 个答案:

答案 0 :(得分:21)

如果您不介意建议,请从Javascript:The Good Parts开始。使用Javascript模式或Javascript Ninja的秘密进行更高级的技术。 Cookbook更多的是解决问题的罐头解决方案,然后是学习资源。

Matt Ball在解释最新情况方面做得很好。如果你是初学者,我也不会试图弄清楚咖喱功能。除此之外,IMO这种咖喱功能很糟糕。这是我改变它的方式

// this is doing binding and partial function application, 
// so I thought bind was a more appropriate name
// The goal is that when you execute the returned wrapped version of fn, its this will be scope
function bind(fn, scope) {
  // arguments is an implicit variable in every function that contains a full list
  // of what was passed in. It is important to note that javascript doesn't enforce arity.
  // since arguments is not a true array, we need to make it one.
  // a handy trick for this is to use the slice function from array,
  // since it will take arguments, and return a real array.
  // we are storing it in a variable, because we will need to use it again.
  var slice =  Array.prototype.slice,
      // use slice to get an array of all additional arguments after the first two
      // that have been passed to this function.
      args = slice.call(arguments, 2);

  // we are returning a function mostly as a way to delay the execution.
  // as an aside, that this is possible in a mainstream language is a minor miracle
  // and a big part of why i love javascript.
  return function() {
    // since functions are objects in javascript, they can actually have methods.
    // this is one of the built in ones, that lets you execute a function in a different
    // context, meaning that the this variable inside the 
    // function will actually refer to the first argument we pass in.

    // the second argument we are jamming together the arguments from the first function
    // with the arguments passed in to this wrapper function, and passing it on to fn.
    // this lets us partially apply some arguments to fn when we call bind.
    return fn.apply(scope, args.concat(slice.call(arguments)));
  }
}
JavaScript虽然精彩,却非常冗长。在定义绑定时不必要地重复var只会增加很多噪音。此外,没有必要像这样痛苦地构建一个真正的数组,切片将采用参数并给你一个真正的数组。特别是在我们使用它两次的情况下,我们实际上想要切掉前两个args。最后,当您申请并且您的第一个arg为null时,JavaScript将为您应用全局对象。没有必要明确地这样做。

IMO我的5行功能体从o'reillys 11行中踢出了废话,IMO它更具可读性。

答案 1 :(得分:15)

// define the curry() function
function curry(fn, scope) {

    // set the scope to window (the default global object) if no scope was passed in.
    scope = scope || window;

    // Convert arguments into a plain array, because it is sadly not one.
    // args will have all extra arguments in it, not including the first 2 (fn, scope)
    // The loop skips fn and scope by starting at the index 2 with i = 2
    var args = [];
    for (var i = 2, len = arguments.length; i < len; ++i) {
        args.push(arguments[i]);
    }

    // Create the new function to return
    return function() {

        // Convert any arguments passed to the this function into an array.
        // This time we want them all
        var args2 = [];
        for (var i = 0; i < arguments.length; i++) {
            args.push(arguments[i]);
        }

        // Here we combine any args originally passed to curry, with the args
        // passed directly to this function.
        //   curry(fn, scope, a, b)(c, d)
        // would set argstotal = [a, b, c, d]
        var argstotal = args.concat(args2);

        // execute the original function being curried in the context of "scope"
        // but with our combined array of arguments
        return fn.apply(scope, argstotal);
    };
}

// Create a function to be curried
function diffPoint(x1, y1, x2, y2) {
    return [Math.abs(x2 - x1), Math.abs(y2 - y1)];
}

// Create a curried version of the diffPoint() function
//   arg1: the function to curry
//   arg2: the scope (passing a falsy value causes the curry function to use window instead)
//   arg3: first argument of diffPoint() to bake in (x1)
//   arg4: second argument of diffPoint() to bake in (y1)
var diffOrigin = curry(diffPoint, null, 3.0, 4.0);

// Call the curried function
// Since the first 2 args where already filled in with the curry, we supply x2 and y2 only
var newPt = diffOrigin(6.42, 8.0);

在这种情况下,根本不使用scope参数。 scope设置this对象的内容。您正在调整的函数不使用this,因此它没有实际效果。调用fn.apply(scope, args)时设置范围,这两个范围都设置要运行的范围并提供传入的参数。

答案 2 :(得分:3)

函数curry允许您bind f函数curryc的第一个参数)到范围curry(func, scope); (第二个参数),带有可选的附加参数(其余参数)。

这意味着这个函数调用:

newFunc

返回一个函数var newFunc = curry(func, scope); // get the new function newFunc(); // now invoke it ,其调用次数为:

scope.func();

相当于:

scope

所有这一切的净效果是this关键字引用func内的scope *。


具体示例时间

假设var scope = {name: 'Inigo Montoya'}; 是一个带有一个属性的简单JS对象:

f

并且scope是一个想要在function f() { return 'My name is ' + scope.name; } 内使用某个值的函数:

f(); // returns 'My name is Inigo Montoya'

并称之为:

curry

嗯,这是一种方法。它有效。

另一种方法是使用f函数。 scope不是scope必须知道引用this对象,而是function f_new() { return 'My name is ' + this.myName; // see the difference? } var sayIt = curry(f, scope); 现在是函数的调用上下文。现在该函数可以使用sayIt关键字!

scope

现在sayIt是一个不关心调用 scope的函数。就像在var scope = { name: 'Inigo Montoya', sayIt: f_new } 对象上定义scope一样,如下所示:

sayIt

...除了实际上 sayIt是如何定义的。 sayIt(); // returns 'My name is Inigo Montoya' 就是这样。现在,我们可以调用null,如下所示:

diffOrigin

还在我身边吗?

。所有这一切的要点是,在您的示例中,curry作为scope = scope || window;运行它的范围提供,因为它不关心范围内的内容< / strong>即可。该行(scopenull表示,如果diffOrigin是一个假值(this是),那么新函数(window就是这种情况)将在全球范围内执行:{{1}}将引用{{1}}。


这对你有意义吗?


*,称为“调用上下文”

答案 3 :(得分:1)

Squeegy发布了一个很好的故障,但我想我也加了我的。

//Things to note, 'arguments' is a special variable in javascript that holds 
//an array like object full of all the things passed into a function.
//You can test this out with a function like this:
//var alertArgs = function(){alert(arguments);};

function curry(fn, scope) {
    //Either use the passed in 'scope' object, or the window object as your scope
    scope = scope || window;
    //Create a new array for storing the arguments passed into this function
    var args = [];
    //Loop through the extra arguments (we start at '2' because the first two
    //arguments were stored in `fn` and `scope` respectively.
    //We store these in the temporary 'args' array.
    //(in the example, args will end up looking like: [3.0, 4.0])
    for (var i = 2, len = arguments.length; i < len; ++i) {
        args.push(arguments[i]);
    }
    //We return the 'curried' function
    return function() {
        //This array is not used. I assume it is an error.
        var args2 = [];
        //We now have a new set of arguments, passed in to the curried function
        //We loop through these new arguments, (in the example, 6.42 and 8.0)
        //and add them to the arguments we have already saved. In the end, we have
        //the args array looking like: [3.0, 4.0, 6.42, 8.0]
        for (var i = 0; i < arguments.length; i++) {
            args.push(arguments[i]);
        }
        //This line isn't needed, because args2 is always blank.
        var argstotal = args.concat(args2);

        //Finally we call the function, passing in the full array of arguments
        return fn.apply(scope, argstotal);
    };
}

//This function takes 4 arguments
function diffPoint(x1, y1, x2, y2) {
    return [Math.abs(x2 - x1), Math.abs(y2 - y1)];
}

//We partially apply the first 2 arguments, so x1 is always 3.0, 
//and y1 is always 4.0
var diffOrigin = curry(diffPoint, null, 3.0, 4.0);

//We can now call 'diffPoint' indirectly, without having to specify 
//3.0, 4.0 as the first 2 arguments.
var newPt = diffOrigin(6.42, 8.0); //produces array with 3

答案 4 :(得分:1)

此版本允许部分应用程序返回新的咖喱函数。

function curry(fn) {
  const args = [];
  let i = 0;
  const n = arguments.length;

  while (++i < n) {
    args.push(arguments[i]);
  }

  // Functions have a 'length' property which tells us their 'arity'
  // 'arity' means the number of arguments a function can take.
  // https://en.wikipedia.org/wiki/Arity
  //
  // Here we count the number of arguments that we have and compare
  // it to the number of arguments the function can take.
  // If the function takes an equal or more amount, we have all our
  // arguments and execute the function.
  //
  return args.length >= fn.length
    // '.apply' will convert an array to a list of arguments.
    // 'null' is the context, which is essentially 'this'
    ? fn.apply(null, args) 
    : function () {
      let i = -1;
      const n = arguments.length;
      const args2 = [];

      while (++i < n) {
        args2.push(arguments[i]);
      }

      // We return a curried function which will get checked
      // for arity. Using recursion, we can keep creating newly
      // partially applied functions which can help us to 'compose'
      // new functions.
      // https://en.wikipedia.org/wiki/Function_composition_%28computer_science%29
      return curry.apply(null, [fn].concat(args.concat(args2)));
    };
}