将参数追加到Javascript函数,而不是前置(Function.prototype.bind)

在Javascript(Node.js上下文)中,我经常使用Function.prototype.bindbind允许改变调用上下文并且可选地提供附加的前置参数。

有没有关于追加参数的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);` ); }; 

在第二个例子中, erreachSeriesasyncCallbackcallback中传递,而其他方法提供了something 。 为了澄清,我正在寻找replacebindAppend从“较短”的例子,将具有function相当于function finished在“较长”的例子。

也许我的devise有缺陷,需要重新devise,或者这只是另一个过早优化的例子。 但是,我寻求的function可以提供以下好处:

  1. 关于可读性简化的代码IMO
  2. 减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