如何使用参数数组调用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) { ... })
但这两种方法都很脏。有什么想法吗?
答案 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指出的那样)。对我来说,解决方案就是简单地切换bind
和apply
:
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