在Javascript(Node.js上下文)中,我定期使用Function.prototype.bind
:bind
允许更改调用上下文,并可选择提供其他前置参数。
是否有针对附加参数的建议?有几次我遇到了在Node.js中追加而不是prepend的需要,所以我可以坚持它的函数签名模式。
现在是一个半实用和简化的例子;我正在使用async module's eachSeries
method。
首先,一个包装回调的实现(工作,但很长的路):
function func(something,callback) {
async.eachSeries(
[1,2,3],
function iterator(item,asyncCallback) {
// do stuff
asyncCallback(err||null);
},
function finished(err) {
// `callback` expects 2 arguments
// `err` should always be the first arg, null or otherwise
// `something` (unrelated to the async series) should be maintained
callback(err,something);
}
);
};
现在有点短暂了:
function func(something,callback) {
async.eachSeries(
[1,2,3],
function iterator(item,asyncCallback) {
// do stuff
asyncCallback(err||null);
},
callback.bindAppend(this,something)
// pseudo-equiv: `callback.bind(this,err,something);`
);
};
在第二个示例中,err
来自eachSeries
的{{1}}回调,而asyncCallback
则通过其他方式提供。为了澄清,我希望将something
替换为“较短”的示例,该示例在“较长”示例中具有与bindAppend
相同的功能。
也许我的设计存在缺陷,需要重新设计,或者这只是过早优化的另一种情况。但是,我寻求的功能可以提供以下好处:
一个答案是从分叉的Function.prototype.bind polyfill中推出自己的。但是,我正在寻找一个我没有看到的本机实现,一个接近本机的解决方案,或者已经完成了艰苦工作的实用程序模块(优化,测试等)。 仅供参考,除前者之外的任何其他解决方案实际上都会使功能优势#2恶化。
答案 0 :(得分:10)
假设我理解了这个问题,我第一次用它进行测试:
Function.prototype.bindAppend = function(context) {
var func = this;
var args = [].slice.call(arguments).slice(1);
return function() {
return func.apply(context, [].slice.call(arguments).concat(args));
}
}
测试:
Function.prototype.bindAppend = function(context) {
var func = this;
var args = [].slice.call(arguments).slice(1);
return function() {
return func.apply(context, [].slice.call(arguments).concat(args));
}
}
describe('bindAppend', function() {
beforeEach(function() {
this.obj = {
value: "a",
func: function() {
return this.value + [].slice.call(arguments).join('');
}
}
});
it('should work properly', function() {
expect(this.obj.func.bindAppend(this.obj, "c")("b")).toEqual("abc");
});
it('should work properly', function() {
expect(this.obj.func.bindAppend(this.obj, "c", "d")("b")).toEqual("abcd");
});
it('should work properly', function() {
expect(this.obj.func.bindAppend(this.obj, "d")("b", "c")).toEqual("abcd");
});
})
工作版本:https://mparaiso.github.io/playground/#/gist/hivsHLuAuV
答案 1 :(得分:2)
没有原生解决方案。但我可以建议你这样的事情:
Function.prototype.bindAppend = function (context) {
var bindArgs = Array.prototype.slice.call(arguments, 1);
var func = this;
return function() {
var funcArgs = Array.prototype.slice.call(arguments);
var args = bindArgs.map(function(arg) {
if (arg === undefined) return funcArgs.shift();
return arg;
}).concat(funcArgs);
func.apply(context, args);
}
}
用法:
function func(something,callback) {
async.eachSeries(
[1,2,3],
function iterator(item,asyncCallback) {
// do stuff
asyncCallback(err||null);
},
callback.bindAppend(this, undefined, something)
// pseudo-equiv: `callback.bind(this,err,something);`
);
};
其他例子:
function a() {console.log.apply(console, arguments)};
var b = a.bindAppend(null, 1, undefined, 3, undefined, 5);
b(2,4,6,7); //1,2,3,4,5,6,7
答案 2 :(得分:2)
为了可维护性,我不会向Function
添加方法来实现此目的。此处问题的其他答案包含两个彼此不兼容的Function.prototype.bindAppend
实现。因此,想象一下创建自己的bindAppend
然后必须使用您的应用程序与一组代码,其作者决定做同样的事情,但使用与您的不兼容的实现。
Polyfills 是好的,因为它们的目标是根据特定标准填写缺失的功能。例如,Function.prototype.bind
的填充物不能自由地偏离ECMA-262,第5版。如果它确实偏离了,那么它可以说是" buggy"并且应该替换为不偏离标准的实现。当一个人实现未由标准定义的方法时,就没有这样的约束。
以下基准测试套件显示了在问题中获得结果的各种方式之间的差异。
var async = require("async");
var Benchmark = require("benchmark");
var suite = new Benchmark.Suite();
function dump (err, something) {
console.log(arguments);
console.log(err, something);
}
function addToSuite(name, makeFinished) {
function func(something, callback) {
async.eachSeries([1,2,3],
function iterator(item, asyncCallback) {
var err;
// do stuff
asyncCallback(err || null);
},
makeFinished(this, something, callback)
);
}
console.log(name, "dump");
func("foo", dump);
console.log("");
suite.add(name, function () {
func("foo", function (err, something) {});
});
}
// Taken from mpm's http://stackoverflow.com/a/23670553/1906307
Function.prototype.bindAppend = function(context) {
var func = this;
var args = [].slice.call(arguments).slice(1);
return function() {
return func.apply(context, [].slice.call(arguments).concat(args));
};
};
addToSuite("anonymous function", function (context, something, callback) {
return function finished(err) {
callback(err, something);
};
});
addToSuite("mpm's bindAppend", function (context, something, callback) {
return callback.bindAppend(context, something);
});
addToSuite("addErr, only one parameter", function (context, something,
callback) {
function addErr(f, something) {
return function (err) {
return f(err, something);
};
}
return addErr(callback, something);
});
addToSuite("addErr, multi param", function (context, something, callback) {
function addErr(f, one, two, three, four, five, six) {
return function (err) {
return f(err, one, two, three, four, five, six);
};
}
return addErr(callback, something);
});
addToSuite("addErr, switch", function (context, something, callback) {
function addErr(f, one, two, three, four, five, six) {
var args = arguments;
return function (err) {
switch(args.length) {
case 1: return f(err);
case 2: return f(err, one);
case 3: return f(err, one, two);
case 4: return f(err, one, two, three);
case 5: return f(err, one, two, three, four);
case 6: return f(err, one, two, three, four, five);
case 6: return f(err, one, two, three, four, five, six);
default: throw Error("unsupported number of args");
}
};
}
return addErr(callback, something);
});
suite
.on('cycle', function(event) {
console.log(String(event.target));
})
.on('complete', function() {
console.log('The fastest is ' + this.filter('fastest').pluck('name'));
})
.run();
输出:
anonymous function dump
{ '0': undefined, '1': 'foo' }
undefined 'foo'
mpm's bindAppend dump
{ '0': 'foo' }
foo undefined
addErr, only one parameter dump
{ '0': undefined, '1': 'foo' }
undefined 'foo'
addErr, multi param dump
{ '0': undefined,
'1': 'foo',
'2': undefined,
'3': undefined,
'4': undefined,
'5': undefined,
'6': undefined }
undefined 'foo'
addErr, switch dump
{ '0': undefined, '1': 'foo' }
undefined 'foo'
anonymous function x 4,137,843 ops/sec ±3.18% (93 runs sampled)
mpm's bindAppend x 663,044 ops/sec ±1.42% (97 runs sampled)
addErr, only one parameter x 3,944,633 ops/sec ±1.89% (91 runs sampled)
addErr, multi param x 3,209,292 ops/sec ±2.57% (84 runs sampled)
addErr, switch x 3,087,979 ops/sec ±2.00% (91 runs sampled)
The fastest is anonymous function
注意:
dump
调用转储来筛选最终回调在没有任何错误时获得的内容。
如果没有错误,mpm的bindAppend
实施将仅使用一个值为callback
的参数调用"foo"
。这与它要替换的原始function finished(err) { callback(err,something);}
回调的行为完全不同。
一切都比mpm' bindAppend
更快。
我不会使用addErr, multi param
实现。我把它放在那里检查这种方法的表现。
addErr
功能比bindAppend
快,但代价是灵活性。它们只向回调添加一个参数。
在一天结束时,我最有可能使用addErr, switch
实现,并且有足够多的案例来处理我的代码需求。如果我需要具有绝对灵活性的东西,对于某些有限的情况,我会使用与bindAppend
类似的内容,但不会使用Function
上的方法。
答案 3 :(得分:0)
lodash _.partial允许你附加参数..
https://lodash.com/docs/4.17.4#partial
function test(a, b, c) {
return a + ' ' + b + ' ' + c;
}
test.bind(null, 1)(2, 3); // 1 2 3
_.partial(test, _, _, 1)(2, 3); // 2 3 1