将参数追加到Javascript函数,而不是前置(Function.prototype.bind)
在Javascript(Node.js上下文)中,我经常使用Function.prototype.bind
: bind
允许改变调用上下文并且可选地提供附加的前置参数。
有没有关于追加参数的build议? 有几次,我遇到了在Node.js中附加而不是预先join的需要,所以我可以坚持它的函数签名模式。
现在为一个半实用和简化的例子; 我正在使用asynchronous模块的eachSeries
方法 。
首先,包装callback的实现(工作,但很长):
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
的asyncCallback
callback中传递,而其他方法提供了something
。 为了澄清,我正在寻找replacebindAppend
从“较短”的例子,将具有function相当于function finished
在“较长”的例子。
也许我的devise有缺陷,需要重新devise,或者这只是另一个过早优化的例子。 但是,我寻求的function可以提供以下好处:
- 关于可读性简化的代码IMO
- 减less和简化堆栈深度
一个答案是将我自己从一个派生的Function.prototype.bind polyfill。 但是,我正在寻找一个我没有看到的本地实现,一个接近原生的解决scheme,或者已经完成了艰苦工作(优化,testing等)的实用程序模块。 仅供参考,除前者之外的任何解决scheme实际上都会恶化特征优势#2。
假设我明白了这个问题,我第一次尝试了这个问题:
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)); } }
testing:
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
没有本地解决scheme。 但是我可以向你提出这样的build议:
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
为了可维护性,我不会添加一个方法来实现这个Function
。 这里的问题的其他答案包含两个不兼容的Function.prototype.bindAppend
实现。 所以想象一下创build你自己的bindAppend
,然后用一个代码体来使用你的应用程序,这个代码体的作者决定做同样的事情,但是使用一个与你不兼容的实现。
因为它们的目标是根据特定的标准填补缺失的function,所以填充物是很好的。 例如, Function.prototype.bind
的polyfill,不能随意偏离ECMA-262,第5版。 如果它偏离了,那么它可以说是“越野车”,应该用一个不偏离标准的实现来代替。 当一个实现的方法不是由一个标准定义的时候,就没有这个约束。
下面的基准testing套件显示了在问题中获得结果的各种方式之间的差异。
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
调用转储来筛选什么是最后的callback得到什么时候没有任何错误。 -
当没有错误时,mpm的
bindAppend
实现将只调用具有值"foo"
一个参数的callback
。 这是一个非常不同的行为,从原来的function finished(err) { callback(err,something);}
callback它是要取代。 -
一切都比mpm的
bindAppend
更快。 -
我不会使用
addErr, multi param
实现。 我把它放在那里来检查如何执行这个方法。 -
addErr
函数比bindAppend
更快,但代价是灵活性。 他们只添加一个参数给callback。
在一天结束的时候,我很可能会使用addErr, switch
使用足够多的case来addErr, switch
实现来处理我的代码的需求。 如果我需要一些具有绝对灵活性的东西,对于一些有限的情况,我会使用类似于bindAppend
东西,而不是作为Function
的方法。
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