将参数附加到Javascript函数,而不是prepend(Function.prototype.bind)

时间:2014-04-30 06:48:40

标签: javascript node.js

在Javascript(Node.js上下文)中,我定期使用Function.prototype.bindbind允许更改调用上下文,并可选择提供其他前置参数。

是否有针对附加参数的建议?有几次我遇到了在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相同的功能。

也许我的设计存在缺陷,需要重新设计,或者这只是过早优化的另一种情况。但是,我寻求的功能可以提供以下好处:

  1. 关于易读性的简化代码IMO
  2. 减少并简化了堆栈深度
  3. 一个答案是从分叉的Function.prototype.bind polyfill中推出自己的。但是,我正在寻找一个我没有看到的本机实现,一个接近本机的解决方案,或者已经完成了艰苦工作的实用程序模块(优化,测试等)。 仅供参考,除前者之外的任何其他解决方案实际上都会使功能优势#2恶化。

4 个答案:

答案 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