使用带有参数数组的Function.prototype.bind?

时间:2014-02-02 05:13:19

标签: javascript functional-programming promise partial-application

如何使用参数数组调用Function.prototype.bind,而不是使用硬编码参数? (不使用ECMA6,因此没有传播运营商)。

我正在尝试将一个promises包装器放在一个使用回调的模块周围,我希望将传递给我的包装器方法的所有参数绑定并绑定它们。然后我想用我自己的回调调用部分应用的绑定函数,它将解析或拒绝一个promise。

var find = function() {
  var deferred, bound;
  deferred = Q.defer();
  bound = db.find.bind(null, arguments);
  bound(function(err, docs) {
    if(err) {
      deferred.fail(err);
    } else {
      deferred.resolve(docs);
    }
  });
  return deferred.promise;
}

但显然这不起作用,因为bind需要参数而不是参数数组。我知道我可以通过将我的回调插入到arguments数组的末尾并使用apply:

来实现
arguments[arguments.length] = function(err, docs) { ... }
db.find.apply(null, arguments);

或者通过迭代arguments数组并为每个参数重新绑定函数:

var bound, context;
for(var i = 0; i < arguments.length; i++) {
   context = bound ? bound : db.find;
   bound = context.bind(null, arguments[i]);
}
bound(function(err, docs) { ... })

但这两种方法都很脏。有什么想法吗?

11 个答案:

答案 0 :(得分:77)

.bind是正常功能,因此您可以在其上调用.apply 您所要做的就是将原始函数作为第一个参数传递,并将所需的THIS变量作为参数数组中的第一项传递:

bound = db.find.bind.apply(db.find, [null].concat(arguments));
//      ^-----^            ^-----^   THIS

是否可以认为更清洁是留给读者的。

答案 1 :(得分:11)

以下是我在所有项目中使用的常见代码片段:

var bind = Function.bind;
var call = Function.call;

var bindable = bind.bind(bind);
var callable = bindable(call);

现在可以使用bindable函数将数组传递给bind,如下所示:

var bound = bindable(db.find, db).apply(null, arguments);

实际上,您可以缓存bindable(db.find, db)以加快绑定速度,如下所示:

var findable = bindable(db.find, db);
var bound = findable.apply(null, arguments);

您可以使用带有或不带参数数组的findable函数:

var bound = findable(1, 2, 3);

希望这有帮助。

答案 2 :(得分:9)

Felix的回答对我不起作用,因为arguments对象实际上并不是一个数组(正如Otts指出的那样)。对我来说,解决方案就是简单地切换bindapply

bound = db.find.apply.bind(db.find, null, arguments);

答案 3 :(得分:4)

对于使用ES6的用户,Babel编译:

db.find.bind(this, ...arguments)

为:

db.find.bind.apply(db.find, [this].concat(Array.prototype.slice.call(arguments)));

我认为说巴贝尔是非常明确的,这是公平的。归功于@ lorenz-lo-sauer,它几乎完全相同。

答案 4 :(得分:1)

为什么不按照你的例子简单地绑定到arguments数组,并让bound()函数像那样处理它,作为数组

根据您的使用情况,您将函数作为bound()的最后一个参数传递,这意味着通过传入实际的参数数组,您可以避免将参数与{{1}中的回调分开,可能会更容易玩。

答案 5 :(得分:1)

通常,此架构足够:

//obj = db
//fnName = 'find'
var args = [this||null].concat(Array.prototype.slice.apply(arguments);
obj[fnName].bind.apply(obj[fnName], args);

答案 6 :(得分:1)

我发现以下清洁剂比接受的答案

Function.bind.apply(db.find, [null].concat(arguments));

答案 7 :(得分:1)

如果有人正在寻找抽象样本:

var binded = hello.apply.bind(hello,null,['hello','world']);

binded();

function hello(a,b){
  console.log(this); //null
  console.log(a); //hello
  console.log(b); //world
}

答案 8 :(得分:0)

只是有另一个想法,部分应用上下文的null值,然后使用apply来调用部分应用的函数。

bound = db.find.bind.bind(null).apply(null, arguments);

这样就不再需要在@Felix的回答中略显怪异的[null].concat()

答案 9 :(得分:0)

明确而简单的答案可能是

Function.apply.bind(this.method, this, arguments);

有点“难以”掌握,但是,整洁。

答案 10 :(得分:0)

顺便说一句,以防有人在仅阅读问题的标题后就来到这里。 我知道 OP 不想使用 ES6 扩展运算符并且明确要求调用 bind(),所以我的回答显然两次偏离主题。

然而,似乎实际需要将 bind() 应用于未绑定的函数(即不是方法),这使得 bind 基本上没有必要。

bind() 必须处理特殊情况(例如为构造函数强制执行 this 的值),这会花费一些计算时间,如果您只想将一些参数应用于普通函数而不是不关心 this 开始。

一个简单的 bind() 版本去掉了健全性检查和针对 this 的 OOP 摆弄可能是:

Function.prototype.naive_bind = function (fixed_this, ...fixed_args) {
    const fun = this;
    return function(...free_args) {
        return fun.call(fixed_this, ...fixed_args, ...free_args);
    }
}

因此您可以编写自己的伪绑定,删除 this 并对您的实际参数进行“部分应用”:

function partial_application (fun, ...applied_args) {
    return function(...free_args) {
        return fun.call(null, ...applied_args, ...free_args);
    }
}

如果您想应用所有参数并为最后一个留出空间,您也可以删除额外的参数:

function total_application (fun, ...applied_args) {
    return function(free_arg) {
        return fun.call(null, ...applied_args, free_arg);
    }
}

如果你的参数在一个数组中,你可以解构它们:

function total_application_from_array (fun, [...applied_args]) {
    return function(free_arg) {
        return fun.call(null, ...applied_args, free_arg);
    }
}

我想您可以使用 apply 而不是 call,但您必须将额外的 free 参数添加到数组中。

function total_application_from_array (fun, applied_args) {
    return function(free_arg) {
        return fun.apply(null, [...applied_args, free_arg]);
    }
}

所以你可以把它用作:

var a = partial_application (console.log, 1, 2, 3);
a(4,5,6);             // 1, 2, 3, 4, 5, 6
a("hello", "world");  // 1, 2, 3, hello, world

a = total_application (console.log, 1, 2, 3);
a()                  // 1, 2, 3, undefined
a(4, 5, 6)           // 1, 2, 3, 4

a = total_application_from_array(console.log, [1,2,3]);
a(4,5,6)             // 1, 2, 3, 4