0

假设我有一个如下的 javascript 函数:

myFunction: function(data, callback){
  //Do stuff
}

假设我现在想向此函数添加另一个参数。让我们打电话 if flag。我也不想到处更改函数调用。在我看来,大多数 javascript 函数都将它们的回调作为最后一个参数。这是否意味着我应该像这样改变函数:

myFunction: function(data, flag, callback){
  //Do stuff
}

还是像这样改变它更好:

myFunction: function(data, callback, flag){
  //Do stuff
}

第一种方法允许回调在最后,但如果我走这条路线,我需要添加一些东西来处理所有将回调作为第二个参数传回的旧调用。就像是:

if (_.isFunction(flag)) {
  onComplete = flag;
  return retAll = false;
}

第二种方法看起来有点奇怪,需要我做这样的事情来处理不包含第三个参数的旧调用:

if (flag == null) {
  flag = false;
}

在这种情况下,是否有一种普遍接受的方式来处理参数的顺序?另外,我是否应该只跟踪对函数的所有调用并更改传入的参数而不是在函数本身中处理它?

4

6 回答 6

0

您可以使用arguments数组,但我建议将可选参数放在最后。此外,您可以使用typeof来确定您的参数类型。

于 2013-10-17T23:29:28.563 回答
0

你可以使用arguments.

这是一个简单、天真的例子:

myFunction: function() {
  var data = arguments[0],
      flag = (_.isBoolean(arguments[1])) ? arguments[1] : arguments[2],
      cb   = (_.isFunction(arguments[2])) ? arguments[2] : arguments[1];

  // stuff
}
于 2013-10-17T23:30:25.107 回答
0
  1. null如果您不确定需要哪些参数,您可以选择“此参数没有值”。
  2. 您可以将原始函数重命名为myFunctionFlag(data, flag, callback)并创建一个新函数myFunction(data, callback),该函数仅myFunctionFlag使用flag. 这在考虑向后兼容性时特别有用,但随着时间的推移代码会变得混乱。
  3. 您可以使用arguments其他答案中显示的内容。
于 2013-10-17T23:31:25.473 回答
0

如果检查所有代码并更改调用签名是可能的,那不是一个坏主意。但是,有时有些代码超出了您的控制范围,您希望保留旧签名但也支持新签名。

例如,具有多个签名/可选参数的方法在 jQuery 等库中非常常见。这是仅基于参数类型进行切换的参数解析器的最小实现。

这与默认对象相结合,以确保所有值都被初始化。如果没有则抛出错误。这比要求始终使用对象语法调用方法更好,因此您可以这样做myFun(a, b, c)而不是myFun({foo: a, bar: b, baz: c})

var myFunSig = typeSignature('data flag callback', '* boolean function');
function myFun() {
    var args = myFunSig(arguments, {
            flag: false // set default
        });

    // all arguments/defaults are properties of 'args'
}

function typeSignature(sigStr, nameStr) {
    var types = sigStr.split(' ');
    var names = nameStr.split(' ');
    return function(args, defaults) {
        var result = $.extend({}, defaults),
            argIdx = 0;
        for(var i = 0, len = types.length; i < len; i++) {
            if(types[i] === '*' || typeof(args[argIdx]) === types[i]) {
                result[names[i]] = args[argIdx++];
            }
        }
        if(argIdx < args.length) { throw 'invalid call'; }
        for(var i = 0, len = names.length; i < len; i++) {
            if(!(names[i] in result)) { throw 'invalid call'; }
        }
        return result;
    };
}

显然,这可以组合某些类型的方法签名,特别是带有可识别类型的可选参数的方法签名。随着事情变得越来越复杂,我猜您可能不得不为每种方法使用自定义代码。

于 2013-10-18T00:59:57.567 回答
0

为我在 op post 评论中建议的装饰器功能提供代码

//http://jsfiddle.net/NsyQT/
var routeEnd = function(fn, len, def) {
    len = len || fn.length;
    return function() {
        var args = Array.prototype.slice.call(arguments);
        if(args.length < len) {
            var last = args.pop();//keep last val in place
            for (var i = 0, l = len-args.length-1; i < l; i++) {
                args.push(def);//default value (undefined)
            }
            args.push(last);
        }
        return fn.apply(this, args);
    }
}

用法

var myFunction = routeEnd(function (data, flag, callback) {
    console.log("flag: %s", flag);
    console.log("cb: %s", callback);
});

myFunction({data: true}, "flagged", function () {});
myFunction({data: true}, function () {});
于 2013-10-17T23:56:30.757 回答
0

Just an option to perhaps get the best of both worlds. Leave all your existing code that calls the function alone, but still add the third parameter as flag to the "myFunction" function:

myFunction: function(data, callback, flag){
  //Do stuff
}

Define a new function that simply calls the original function for the places where the flag IS going to be passed:

myFunctionSane: function(data, flag, callback) {
  myFunction(data, callback, flag);
}

You still need to add the check for the flag in the original "myFunction", but any NEW places where you want to call with all three parameters, you can call "myFunctionSane" with the callback as the third argument.

EDIT: Throwing another idea out would be to leave the original signature completely in tact and pass the original "data" element for the old calls (no changes needed), but pass a json object as the data element for the new calls such as:

myFunction( { "data": myData, "flag": myFlag }, myCallback );

In your function, you would just have to test if the element "data.data" is defined to know whether or not a legacy function call is being made or a new call that passes JSON.

于 2013-10-17T23:34:49.510 回答