JavaScript中bind和curry的用法有什么区别?

26

我试图理解 currybind 之间的区别。

bind 的实现如下:

/*1*/   Function.prototype.bind = function ()
/*2*/   {
/*3*/        var fn = this,
/*4*/           args = Array.prototype.slice.call(arguments);
/*5*/       var object = args.shift();
/*6*/       return function ()
/*7*/       {
/*8*/           return fn.apply(object, 
/*9*/                           args.concat(Array.prototype.slice.call(arguments)))
/*10*/       };
/*11*/   }

curry 的实现是:

/*1*/   Function.prototype.curry = function ()
/*2*/   {
/*3*/       var fn = this,
/*4*/           args = Array.prototype.slice.call(arguments);
/*5*/       return function ()
/*6*/       {
/*7*/           return fn.apply(this, 
/*8*/                           args.concat(Array.prototype.slice.call(arguments)));
/*9*/       };
/*10*/   };

我已经知道了curry不是内置函数(不像bind在IE9+中是内置的)。但是,为什么我听到人们一直谈论curry,当他们可以简单地使用bind操作呢?唯一的区别在于上下文,这实际上只在bind函数中才能找到。

差异

图片描述

例如:

假设我有这个函数:

function add(x,y,z)
{
  return x+y+z;
}

我可以用柯里化来实现:

alert(add.curry(2).curry(1)(4)) //7

但我也可以用以下方式完成:

alert(add.bind(undefined,2).bind(undefined,1)(4)) //7

我不明白为什么会有curry这个函数,既然可以给bind函数添加一个虚拟上下文。

我错过了什么吗?


5
据我所知,在Chrome版本33.0.1750.117 m中,curry函数不存在。搜索JavaScript curry并没有在浏览器的扩展中找到结果。这个函数是从哪里来的?你是否使用了外部库? - user2357112
3
那你在哪里运行这个代码?你没有提到node.js或其他特定的环境。(node.js似乎也没有curry方法。) - user2357112
2
我不明白你的问题。bind 用于将函数绑定到上下文,而 curry 用于创建部分应用的函数。它们有不同的用法,仅仅因为你可以使用 bindundefined 进行柯里化并不意味着你应该这样做,因为这会使你的代码变得晦涩难懂。 - Paolo Moretti
1
你在质疑为什么术语“柯里化”存在,只是因为JavaScript有一个.bind()函数?我真的不明白你在说什么。许多函数式编程概念在JS拥有.bind()之前就已经存在了,事实上,在JS出现之前就已经存在了。难道它们现在应该停止存在吗? - cookie monster
3
如果你想了解这些术语的起源,以及为什么JS的方法叫做.bind()而不是curry(),我猜测这是因为.bind()确实提供了绑定this值的选项,这与柯里化并没有太大关系。我认为这是.bind()的主要目的,因为this值在回调函数中动态变化,并且处理起来可能很麻烦。我认为.bind()的想法可能来自于prototypejs库,但我不完全确定。 - cookie monster
显示剩余4条评论
3个回答

23

bind会强制你把一个上下文(context)附加到函数中,而使用curry可以延迟指定函数上下文的时间,直到调用柯里化后的函数时再指定上下文,这在许多情况下很有用。
考虑以下示例(不是完美的示例,只是为了说明这个想法):

function Query(sessionKey, dataBuilder) {
  this.sessionKey = sessionKey;
  this.url = "http://www.example.com/search";
  this.dataBuilder = dataBuilder
  this.search = function (term) {
    $.ajax({
      type: "POST",
      url: this.url,
      data: this.dataBuilder(term);
    })
  }
}

function dataBuilder(entity, query) {
  var payload = JSON.stringify({
    'entity': entity,
    'searchTerm': query
    'session': this.sessionKey // will be always undefined if bind(undefined,...) is used
  });
  return payload
}
var bindEx= dataBuilder.bind(undefined, "username");
var curryEx= dataBuilder.curry("username");

var usernameQuery = new Query("id1234",bindEx); // won't work, this.sessionKey will be undefined
usernameQuery = new Query("id1234",curryEx); // will  work, this.sessionKey will be id1234 in the DataBuilder

23

意图有所不同。

柯里化是将参数数量减少,通常是为了避免反复调用一个函数并传入相同的初始参数。例如:

var celsiusToKelvin = add.curry(273.15);

bind() 用于确保函数绑定到对象上。 它还提供了一个柯里化工具,所以你可以使用 bind() 来进行柯里化,但如果你想进行柯里化,curry() 的参数更少且表现出你的意图。


1
我同意这是关于意图的问题。换句话说,bind关闭函数,而curry则保持其开放状态。当然,正如OP所观察到的那样,这两个操作在实际上是几乎相同的,你可以通过绑定所有参数来使用curry关闭一个函数。但是一般的假设是柯里化的函数不会被自己调用,而绑定的函数会被调用。 - harpo

2

我认为这与兼容旧浏览器有关,因为bind仅自ECMAScript 5起可用。

请参阅此处的.bind()支持列表:http://kangax.github.io/es5-compat-table/

另外,从我听说的大部分人来看,他们仍然使用curry,因为它看起来更简洁,不需要在参数中添加额外的undefined


柯里化的实现还可以包括语法糖(不是 curriedFunc(3,4)(2),而是 curriedFunc(3,4,2)),并且还可以选择将函数封闭为一组特定数量的参数(可以是最初定义的数量,也可以是在柯里化定义时设置的任意数量)。 - Dtipson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接