JavaScript中有更好的可选函数参数的方式吗?

863

我一直是这样处理 JavaScript 中的可选参数:

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

  // Do stuff
}

有没有更好的方法?

在什么情况下使用像||这样的方式会失败?


10
http://www.openjs.com/articles/optional_function_arguments.php - slf
6
很有意思。将参数作为关联数组传递似乎是处理超过几个参数的好方法。 - Mark Biek
1
这正是我在大多数情况下所做的。我的具有超过1个参数的函数期望一个对象字面量。 - Andrew Hedges
2
@slf 我相信你的评论被忽略了,所以我为那些在谷歌上搜索的人添加了一个关于JavaScript中arguments的答案。 - Justus Romijn
2
参见:https://dev59.com/WXNA5IYBdhLWcg3wmfO5 - Malcolm
显示剩余2条评论
28个回答

1101

如果传递了optionalArg参数但其估值为false,那么你的逻辑会失败 - 尝试使用以下替代方案

if (typeof optionalArg === 'undefined') { optionalArg = 'default'; }

或者一个替代的习语:

optionalArg = (typeof optionalArg === 'undefined') ? 'default' : optionalArg;

使用最能传达你意图的习语!


84
为了保险起见,我会使用三个等号('...lArg === "und...')来比较。 - Jarrod
36
最好将使用 "===" 建立为一种习惯,这样就不必记得在哪种情况下它是“安全”的。 - jinglesthula
46
为什么在这里使用三目运算符?第二个子句不仅仅是将可选参数设置为optionalArg吗?我认为只需一个简单的IF就可以了:如果(typeof optionalArg ===“undefined”),则optionalArg =“defaultValue”。 - Shane N
14
我认为它更好地表达了意图 - typeof 不会评估运算,只会检查其类型,这正是我们感兴趣的。此外,评估速度会慢约40%(http://jsperf.com/stackoverflow148901)。 - Paul Dixon
13
好的,你不是在谈论可选参数 - 你确实正在传递一个参数,所以这个解决方案不适用于你的情况。 NaN并不意味着"未定义" - 它意味着你已经传递了一个数字,但它的值是NaN。请参见https://dev59.com/PXA75IYBdhLWcg3wlqIT获取更多信息。 - Paul Dixon
显示剩余14条评论

193

ECMAScript 2015(也称为"ES6")中,您可以在函数声明中声明默认参数值:

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

MDN上的这篇文章中可以了解更多关于它们的内容。

目前只有Firefox支持默认参数,但由于标准已经完成,预计支持会迅速提高。


编辑(2019年06月12日):

默认参数现在受到现代浏览器的广泛支持。
所有版本的Internet Explorer都不支持此功能。然而,ChromeFirefoxEdge目前都支持它。


8
这对我来说已经足够证明JavaScript确实是邪恶的了。@ThoAppelsin - Zander Rootman
8
JavaScript 只对那些不理解它的人来说才是邪恶的(我并不是说你不理解,但这是我的观点)。我认为它的范式在概念上非常强大(实现也在不断改进)。 - trusktr
9
你好,Python语法。很高兴再次见到你。 - Hubert Grzeskowiak
2
@HubertGrzeskowiak 和 C# 语法(用于默认值,如果没有其他内容)。 - Zev Spitz
5
同样的方法不仅适用于Python或C#,也适用于许多其他语言 :) 这是默认参数值的常见做法。 - Ilgıt Yıldırım
3
由于ES6是当前的标准,这是正确的答案。 - Outside_Box

126

我认为这是最简单、最易读的方法:

if (typeof myVariable === 'undefined') { myVariable = 'default'; }
//use myVariable here

在我看来,Paul Dixon的答案不如这个易读,但这取决于个人喜好。

Insin的答案更加高级,但对于大型函数来说更有用!

编辑 11/17/2013 9:33pm:我创建了一个名为Node.js的包,使“overload”函数(方法)更容易,称为parametric


3
我让这个回答和被接受的回答生死斗。根据我进行的几次测试,被接受的回答的三元方法在速度上略慢一些:http://jsperf.com/optional-function-parameters-ternary-vs-manual - Jeromy French

45

如果你需要插入一个字面意义上的 NULL,可能会遇到一些问题。除此之外,我认为你可能走在了正确的道路上。

有些人选择另一种方法,即使用关联数组遍历参数列表。这看起来更整洁,但我想它可能会稍微(非常微小)增加一些处理/内存负担。

function myFunction (argArray) {
    var defaults = {
        'arg1'  :   "value 1",
        'arg2'  :   "value 2",
        'arg3'  :   "value 3",
        'arg4'  :   "value 4"
    }

    for(var i in defaults) 
        if(typeof argArray[i] == "undefined") 
               argArray[i] = defaults[i];

    // ...
}

4
为什么不使用 hasOwnProperty 而是用 == "undefined" 呢? - Cris Stringfellow
为什么不用 if (!(i in argArray)) 呢?为了完整性,在这个 if 语句之前,应该检查正确的参数类型,例如 if (typeof argArray !== "object") argArray = []; - Bart

33
理想情况下,您应该重构代码以传递一个对象并与默认对象merge,这样传递参数的顺序就不重要了(请参见下面答案的第二部分)。
但是,如果您只想要一些快速、可靠、易于使用且不臃肿的东西,请尝试以下方法:

任意数量默认参数的简洁快速修复方法

  • 它具有优雅的可扩展性:每个新默认值只需最少的额外代码
  • 您可以将其粘贴到任何位置:只需更改所需参数和变量的数量
  • 如果要将undefined传递给具有默认值的参数,则通过此方式,该变量将设置为undefined。本页面上的大多数其他选项都将使用默认值替换undefined

这是一个为三个可选参数(带两个必需参数)提供默认值的示例:

function myFunc( requiredA, requiredB,  optionalA, optionalB, optionalC ) {

  switch (arguments.length - 2) { // 2 is the number of required arguments
    case 0:  optionalA = 'Some default';
    case 1:  optionalB = 'Another default';
    case 2:  optionalC = 'Some other default';
    // no breaks between cases: each case implies the next cases are also needed
  }

}

简单演示。这类似于roenving的答案,但更容易扩展到任意数量的默认参数,更容易更新,并且使用arguments而不是Function.arguments


传递和合并对象以获得更大的灵活性

像许多默认参数的方法一样,上述代码不能传递无序的参数,例如传递optionalC但将optionalB留给其默认值。

一个好的选择是传递对象并与默认对象合并。这也有利于可维护性(只需注意保持代码可读性,以便未来的协作者不会猜测您传递的对象可能包含的内容)。

使用jQuery的示例。如果您不使用jQuery,则可以使用Underscore的_.defaults(object, defaults)浏览这些选项

function myFunc( args ) {
  var defaults = {
    optionalA: 'Some default',
    optionalB: 'Another default',
    optionalC: 'Some other default'
  };
  args = $.extend({}, defaults, args);
}

这是其简单示例


我不确定我理解这个。如果参数实际上被给出,它也会替换值吗?!噢 - jeromej
嘿,这是我见过的最干净的方法。唯一的问题在于:http://jsperf.com/optional-parameters-typeof-vs-switch。Switch 显然非常慢。 - JDC
6
我觉得每秒 23.6 百万次操作并不算慢......虽然 typeof 很快,但这并不意味着 switch 很慢——只是没有那么快而已。 - user56reinstatemonica8

19

你可以使用一些不同的方案来实现这个。我总是通过检查 arguments.length 来测试:

function myFunc(requiredArg, optionalArg){
  optionalArg = myFunc.arguments.length<2 ? 'defaultValue' : optionalArg;

  ...

因此,它不可能失败,但我不知道你的方法是否有失败的可能性,现在我想不出任何情况会导致它失败...

然后保罗提供了一个失败的场景!-)


我很惊讶没有人提到使用参数数组! - EvilSyn
2
什么是“失败场景”?如果传递false、null、0等值,这不会改变参数数组的长度。否则,使用switch(myFunc.arguments.length)是很好和灵活的。 - user56reinstatemonica8
哦,我明白了 - 你是指原帖中的一个失败场景,而不是你的解决方案中的一个失败场景。 - user56reinstatemonica8

14

和Oli的回答类似,我使用一个参数对象和一个定义默认值的对象。再加上一点点语法糖...

/**
 * 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的构造函数一样)。
  • 继承层次结构中的子对象可以根据需要自定义其父级构造函数的参数,强制执行自己的默认值或确保始终使用某个特定值。

1
我讨厌复杂的函数和冗长的文档。 - EmRa228

9

松散类型检查

编写容易,但是0''falsenullundefined将被转换为默认值,这可能不是预期的结果。

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') {

}

检查参数变量 - 捕捉所有情况,但是写起来最笨拙。在宽松模式下,由于使用arguments可能会产生负面的性能影响(尽管通常并不重要)。 - T.J. Crowder

8
如果您广泛使用默认设置,那么这种方式看起来更易读:
function usageExemple(a,b,c,d){
    //defaults
    a=defaultValue(a,1);
    b=defaultValue(b,2);
    c=defaultValue(c,4);
    d=defaultValue(d,8);

    var x = a+b+c+d;
    return x;
}

只需在全局作用域中声明此函数。

function defaultValue(variable,defaultValue){
    return(typeof variable!=='undefined')?(variable):(defaultValue);
}

使用模式 fruit = defaultValue(fruit,'Apple');

*PS 你可以将defaultValue函数重命名为一个简短的名称,但不要使用default,它是JavaScript中的保留字。


+1 因为我本来要发布我的解决方案,它与以下代码完全相同:function defaultFor(arg, val) { return typeof arg !== 'undefined' ? arg : val; } - Camilo Martin

6

使用ES2015/ES6,您可以利用Object.assign来替代$.extend()_.defaults()

function myFunc(requiredArg, options = {}) {
  const defaults = {
    message: 'Hello',
    color: 'red',
    importance: 1
  };

  const settings = Object.assign({}, defaults, options);

  // do stuff
}

您可以像这样使用默认参数
function myFunc(requiredArg, { message: 'Hello', color: 'red', importance: 1 } = {}) {
  // do stuff
}

1
将来,您甚至可以使用对象展开更短地编写此代码(目前Edge不支持!)-演示fiddle:https://jsfiddle.net/L7evm21q/ 基本代码:`function test(options) { options = { someDefaultParam: '', ...options };/* do stuff */}` 将来的参考链接:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Browser_compatibility - jave.web

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接