JavaScript 咖喱

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5176313/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-25 16:16:32  来源:igfitidea点击:

JavaScript curry

javascript

提问by mjmitche

I`m a newbie at JavaScript trying to understand this tutorial about currying from Oreilly JavaScript Cookbook.

我是 JavaScript 的新手,试图理解 Oreilly JavaScript Cookbook 中关于柯里化的教程。

Could someone be kind enough to explain this program in detail step by step in plain language. Please make sure to explain the "null" argument passed in the second last line of the program. Thank you in advance if you can help.

有人可以用简单的语言一步一步地详细解释这个程序。请确保解释在程序的倒数第二行中传递的“空”参数。如果您能提供帮助,请提前致谢。

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

回答by Matt Briggs

If you dont mind a suggestion, start with Javascript: The Good Parts. Follow that up with either Javascript Patterns, or Secrets of the Javascript Ninja for more advanced techniques. Cookbooks are more for canned solutions to problems then a learning resource.

如果您不介意建议,请从 Javascript: The Good Parts 开始。跟随 Javascript Patterns 或 Secrets of the Javascript Ninja 以获得更高级的技术。食谱更像是解决问题的罐头解决方案,而不是学习资源。

Matt Ball did a good job explaining whats going on. If you are a beginner, I wouldn't sweat trying to figure out curry functions anyways. That aside, IMO this curry function is terrible. This is how I would change it

马特鲍尔很好地解释了正在发生的事情。如果您是初学者,无论如何我都不会出汗试图找出咖喱函数。除此之外,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, while wonderful, is horribly verbose. Needlessly repeating var while defining your bindings just adds a lot of noise. Also, there is no need to painfully build a real array like that, slice will take arguments and give you a real array back. Especially in this case where we are using it twice, AND we actually want to slice out the first two args anyways. Finally, when you apply and your first arg is null, JavaScript will apply the global object for you. There is no need to do that explicitly.

JavaScript 虽然很棒,但非常冗长。在定义绑定时不必要地重复 var 只会增加很多噪音。此外,没有必要像那样痛苦地构建一个真正的数组, slice 将接受参数并返回一个真正的数组。特别是在我们使用它两次的情况下,我们实际上想要切出前两个参数。最后,当您申请并且您的第一个 arg 为空时,JavaScript 将为您申请全局对象。没有必要明确地这样做。

IMO my 5 line function body kicks the crap out of o'reillys 11 lines, and IMO it is much more readable.

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

回答by Alex Wayne

// 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);

In this case the scopeargument isn't used at all. scopesets what the thisobject is. The function you are currying doesn't use thisso it has no real effect. The scope is set when fn.apply(scope, args)is called, which both sets the scope to run in and provides arguments to pass in.

在这种情况下,scope根本不使用该参数。 scope设置this对象是什么。您正在使用的函数没有使用,this因此没有实际效果。作用域在fn.apply(scope, args)被调用时设置,它既设置要运行的作用域,又提供要传入的参数。

回答by Matt Ball

The function curryallows you to binda function f(the first parameter to curry) to a scope c(the second parameter), with optional additional arguments (the rest of the parameters).

该函数curry允许您使用可选的附加参数(其余参数)函数f(第一个参数到curry绑定到范围c(第二个参数)。

That means that this function call:

这意味着这个函数调用:

curry(func, scope);

returnsa function newFuncwhose invocation:

返回一个函数,newFunc其调用:

var newFunc = curry(func, scope); // get the new function
newFunc(); // now invoke it

is equivalent to this:

相当于:

scope.func();

The net effect of all this is for the thiskeyword to refer to scope* inside of func.

所有这些的最终效果是this关键字scopefunc.



Concrete example time

具体实例时间

Let's say that scopeis a simple JS object with one property:

假设这scope是一个具有一个属性的简单 JS 对象:

var scope = {name: 'Inigo Montoya'};

and that fis a function which wants to use some value inside of scope:

f是一个想要使用以下值的函数scope

function f() {
    return 'My name is ' + scope.name;
}

and call it, like this:

并调用它,如下所示:

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

Well, that's one way to do it. It works.

嗯,这是一种方法。有用。

Another way to do it would be using the curryfunction. Instead of fhaving to know to reference the scopeobject, scopeis now the function's invocation context. Now the function can use the thiskeyword!

另一种方法是使用该curry函数。现在不必f知道引用scope对象,scope而是函数的调用上下文。现在函数可以使用this关键字了!

function f_new() {
    return 'My name is ' + this.myName; // see the difference?
}

var sayIt = curry(f, scope);

Now sayItis a function that doesn't care whatscopeis called. It's like sayItis defined on the scopeobject, like this:

现在sayIt是一个不关心调用什么的函数scope。它就像是sayItscope对象上定义的,像这样:

var scope = { name: 'Inigo Montoya',
              sayIt: f_new }

...except that's not actuallyhow scopeis defined. sayItjust works that way. Now, we can call sayIt, like this:

...除了这实际上不是如何scope定义的。sayIt就是这样。现在,我们可以sayIt像这样调用:

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


Still with me?

还在我这儿?

Phew. The point of all this is to say that, in your example, nullis being provided as the scope for diffOriginto run it, because it doesn't care about what's in the scope. The line (in curry) scope = scope || window;means that, if scopeis a falsy value (which nullis) then the new function (diffOriginin this case) will execute in global scope: thiswill refer to window.

。所有这一切的重点是说,在您的示例中,null被提供为diffOrigin运行它的范围,因为它不关心 scope 中的内容。行 (in curry)scope = scope || window;表示,如果scope是假值(即null),则新函数(diffOrigin在本例中)将在全局范围内执行:this将引用window.



Does that make sense to you?

这对你有意义吗?



*which is called the "invocation context"

*这称为“调用上下文”

回答by david

Squeegy posted a good breakdown, but I figured I'd add mine too.

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

回答by SeanJM

This version allows for partial application which returns a new curried function.

此版本允许部分应用程序返回一个新的柯里化函数。

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)));
    };
}