我一直在处理JavaScript中的可选参数,如下所示:

function myFunc(requiredArg, optionalArg){
  optionalArg = optionalArg || 'defaultValue';

  // Do stuff
}

有更好的方法吗?

这样使用||会失败吗?


当前回答

似乎最安全的方法-在决定使用默认值之前处理所有提供的错误类型的参数-是检查所调用函数中是否存在可选参数。

依赖于arguments对象成员创建(如果缺少参数,甚至不会创建),无论它可能被声明,我们都可以这样编写函数:

  function myFunc(requiredArg, optionalArg){
        optionalArg = 1 in arguments ? optionalArg : 'defaultValue';
  //do stuff
  }

利用此行为:每当我们需要确保函数获得其过程中所需的某个值时,我们都可以安全地检查参数列表中的任何缺失值。

在下面的演示代码中,我们将故意将一个无类型和无值的undefined作为默认值,以便能够确定它是否会在错误的参数值(如0 false等)上失败,或者它的行为是否符合预期。

function argCheck( arg1, arg2, arg3 ){

       arg1 = 0 in arguments || undefined;
       arg2 = 1 in arguments || false;
       arg3 = 2 in arguments || 0;
   var arg4 = 3 in arguments || null;

   console.log( arg1, arg2, arg3, arg4 ) 
}

现在,检查几个错误的参数值,看看是否正确检测到它们的存在,并因此计算为真:

argCheck( "", 0, false, null );
>> true true true true

这意味着-它们没有将/识别为预期的参数值。这里我们检查了所有参数的缺失,根据我们的算法,即使它们是假的,也应该获取它们的默认值。

argCheck( );
>> undefined false 0 null

如我们所见,参数arg1、arg2、arg3和未声明的arg4按顺序返回其确切的默认值。因为我们现在已经确定了它的有效性,所以我们可以重写函数,该函数实际上可以像第一个示例中那样使用它们,方法是使用if或三元条件。

对于具有多个可选参数的函数,-循环,可能会为我们节省一些时间。但是,由于如果不提供参数名称的值,则参数名称不会被初始化,因此即使我们以编程方式编写了默认值,我们也无法再通过名称访问它们,因此只能通过参数[索引]访问它们,这对代码的可读性来说是无用的。

但除了在某些编码情况下可能完全可以接受的这种不便之外,还有一个未知的问题,即多个任意数量的参数默认值。这可能也应该被认为是一个错误,因为我们不能再跳过参数,因为我们曾经可以在不提供值的情况下,使用如下语法:

argCheck("a",,22,{});

因为它会扔!这使得我们无法用所需默认值的特定错误类型替换参数。这是愚蠢的,因为arguments对象是一个类似数组的对象,并且应该支持这种语法和约定,无论是原生的还是默认的!

由于这个短视的决定,我们不能再希望编写这样的函数:

function argCheck( ) {
    var _default = [undefined, 0, false, null ],
        _arg = arguments;

 for( var x in _default ) {
         x in _arg ? 1 : _arg[x] = _default[x];
        }
    console.log( _arg[0],_arg[1],_arg[2],_arg[3] );
}

在这种情况下,我们将能够在arguments行中写入所需类型的每个默认值,并且至少能够通过args.index访问它们。

例如,此函数调用将产生:

argCheck();
>>undefined 0 false null

如我们的默认参数值数组中所定义的。但是,仍有可能出现以下情况:

argCheck({})
>>Object {  } 0 false null

argCheck({}, [])
>>Object {  } Array [  ] false null

但遗憾的是:

 argCheck("a",,,22);
 >>SyntaxError: expected expression, got ','

否则将记录:

>>a 0 false 22

但那是一个更好的世界!然而,对于最初的问题,最上面的函数会做得很好。例如。:

function argCheck( arg, opt ) {
         1 in arguments ? 1 : opt = "default";
         console.log( arg, opt );
}

p.s.:很抱歉,在编写参数输入时没有保留所选默认值的类型。

其他回答

这些版本比操作员版本的类型短。

function foo(a, b) {
    a !== undefined || (a = 'defaultA');
    if(b === undefined) b = 'defaultB';
    ...
}

在ECMAScript 2015(又名“ES6”)中,您可以在函数声明中声明默认参数值:

function myFunc(requiredArg, optionalArg = 'defaultValue') {
    // do stuff
}

关于MDN的更多信息,请参阅本文。

目前只有Firefox支持这一点,但随着标准的完成,预计支持会迅速提高。


编辑(2019-06-12):

默认参数现在被现代浏览器广泛支持。所有版本的Internet Explorer都不支持此功能。然而,Chrome、Firefox和Edge目前都支持它。

松散类型检查

易于编写,但0、“”、false、null和undefined将转换为默认值,这可能不是预期的结果。

function myFunc(requiredArg, optionalArg) {
    optionalArg = optionalArg || 'defaultValue';
}

严格的类型检查

更长,但涵盖大多数情况。只有当我们传递undefined作为参数时,它才会错误地分配默认值。

function myFunc(requiredArg, optionalArg) {
    optionalArg = typeof optionalArg !== 'undefined' ? optionalArg : 'defaultValue';
}

正在检查参数变量

能捕捉所有的情况,但写起来最笨拙。

function myFunc(requiredArg, optionalArg1, optionalArg2) {
    optionalArg1 = arguments.length > 1 ? optionalArg1 : 'defaultValue';
    optionalArg2 = arguments.length > 2 ? optionalArg2 : 'defaultValue';
}

ES6

不幸的是,目前浏览器支持非常差

function myFunc(requiredArg, optionalArg = 'defaultValue') {

}

与Oli的答案类似,我使用了一个参数Object和一个定义默认值的Object。加一点点糖。。。

/**
 * Updates an object's properties with other objects' properties. All
 * additional non-falsy arguments will have their properties copied to the
 * destination object, in the order given.
 */
function extend(dest) {
  for (var i = 1, l = arguments.length; i < l; i++) {
    var src = arguments[i]
    if (!src) {
      continue
    }
    for (var property in src) {
      if (src.hasOwnProperty(property)) {
        dest[property] = src[property]
      }
    }
  }
  return dest
}

/**
 * Inherit another function's prototype without invoking the function.
 */
function inherits(child, parent) {
  var F = function() {}
  F.prototype = parent.prototype
  child.prototype = new F()
  child.prototype.constructor = child
  return child
}

……这可以做得更好一点。

function Field(kwargs) {
  kwargs = extend({
    required: true, widget: null, label: null, initial: null,
    helpText: null, errorMessages: null
  }, kwargs)
  this.required = kwargs.required
  this.label = kwargs.label
  this.initial = kwargs.initial
  // ...and so on...
}

function CharField(kwargs) {
  kwargs = extend({
    maxLength: null, minLength: null
  }, kwargs)
  this.maxLength = kwargs.maxLength
  this.minLength = kwargs.minLength
  Field.call(this, kwargs)
}
inherits(CharField, Field)

这种方法有什么好处?

您可以根据需要省略任意多个参数-如果您只想重写一个参数的值,则可以只提供该参数,而不必显式传递undefined。例如,如果有5个参数,并且您只想自定义最后一个参数,则必须使用其他建议的方法。当为从另一个继承的对象使用构造函数Function时,很容易接受所继承对象的构造函数所需的任何参数,因为您不必在构造函数签名中命名这些参数,或者甚至提供自己的默认值(让父对象的构造函数为您实现,正如上面CharField调用Field的构造函数时所看到的那样)。继承层次结构中的子对象可以根据需要为其父构造函数自定义参数,强制使用自己的默认值或确保始终使用某个值。

function foo(requiredArg){
  if(arguments.length>1) var optionalArg = arguments[1];
}