多参数与选项对象


157

当创建带有多个参数的JavaScript函数时,我总是面临这样的选择:传递参数列表与传递选项对象。

例如,我正在编写一个将nodeList映射到数组的函数:

function map(nodeList, callback, thisObject, fromIndex, toIndex){
    ...
}

我可以改用这个:

function map(options){
    ...
}

其中options是一个对象:

options={
    nodeList:...,
    callback:...,
    thisObject:...,
    fromIndex:...,
    toIndex:...
}

推荐哪种方法?是否有关于何时使用一个与另一个的指南?

[更新]对于选项对象似乎已经达成共识,所以我想添加一条评论:在我的案例中,我很想使用参数列表的原因之一是其行为与JavaScript一致内置array.map方法。


2
第二个选项为您提供了命名参数,在我看来这是一件好事。
Werner KvalemVesterås2012年

它们是可选参数还是必需参数?
我讨厌懒惰的2012年

@ user1689607在我的示例中,后三个是可选的。
Christophe

因为最后两个参数非常相似,所以如果用户仅传递了另一个参数,则您将永远无法真正知道要使用哪个参数。因此,您几乎需要命名参数。但是,我希望您能维护一个与本机API类似的API。
我讨厌懒惰的2012年

1
如果您的函数执行类似的操作,则按照本机API进行建模不是一件坏事。归结为“什么使代码最容易阅读”。 Array.prototype.map有一个简单的API,不应让任何半经验的编码人员感到困惑。
杰里米·J·斯塔彻

Answers:


160

像许多其他方法一样,我通常更喜欢将options objecta 传递给函数,而不是传递一长串参数,但这实际上取决于确切的上下文。

我将代码的可读性用作石蕊测试。

例如,如果我有此函数调用:

checkStringLength(inputStr, 10);

我认为代码的可读性很高,传递单个参数也很好。

另一方面,有些函数具有如下调用:

initiateTransferProtocol("http", false, 150, 90, null, true, 18);

除非您进行一些研究,否则完全不可读。另一方面,这段代码很好看:

initiateTransferProtocol({
  "protocol": "http",
  "sync":      false,
  "delayBetweenRetries": 150,
  "randomVarianceBetweenRetries": 90,
  "retryCallback": null,
  "log": true,
  "maxRetries": 18
 });

它更多地是一门艺术,而不是一门科学,但如果我必须说出经验法则:

在以下情况下,请使用options参数:

  • 您有四个以上的参数
  • 任何参数都是可选的
  • 您曾经必须查找该函数以找出需要使用的参数
  • 如果有人在尖叫“ ARRRRRG”时试图勒死您!

10
好答案。这取决于。当心布尔陷阱ariya.ofilabs.com/2011/08/hall-of-api-shame-boolean-trap.html
Trevor Dixon

2
哦,是的...我已经忘记了那个链接。这确实让我重新考虑了API的工作原理,甚至在得知自己做过愚蠢的事情后甚至重写了几段代码。谢谢!
杰里米·J·斯塔彻

1
“您曾经必须查找该函数以找出需要使用的参数”-而是使用options对象,您是否不必总是查找该方法来确定需要哪些键以及调用什么键。IDE中的Intellisense不会显示此信息,而params会显示。在大多数IDE中,您只需将鼠标悬停在该方法上,它将向您显示参数。
simbolo 2015年

1
如果你们有兴趣在该位的“编码最佳实践”的性能结果,这里有一个jsPerf测试两种方式:jsperf.com/function-boolean-arguments-vs-options-object/10 注意在小扭第三种选择,我使用“预设”(常量)选项对象,当您在开发时(简而言之)用相同的设置进行多次调用(在运行时的生命周期内,例如网页)时,可以完成此操作:当您的选项值在源代码中被硬编码时)。
Ger Hobbelt

2
@Sean老实说,我完全不再使用这种编码风格。我已经切换到TypeScript并使用命名参数。
杰里米·J·斯塔彻

28

多个参数主要用于强制性参数。他们没有错。

如果您有可选参数,它将变得很复杂。如果其中一个依赖于另一个,以便它们具有一定的顺序(例如,第四个需要第三个),则仍应使用多个参数。几乎所有本机的EcmaScript和DOM方法都是这样工作的。一个很好的例子是openXMLHTTPrequests方法,其中最后3个参数是可选的-规则就像“没有用户就没有密码”(另请参见MDN docs)。

选项对象在两种情况下派上用场:

  • 您有太多的参数,令人感到困惑:“命名”将为您提供帮助,您不必担心它们的顺序(尤其是它们可能会更改)
  • 您有可选参数。这些对象非常灵活,无需排序即可传递所需的东西,而无需传递其他任何东西undefined

您的情况下,我建议map(nodeList, callback, options)nodelist并且callback是必需的,其他三个参数仅偶尔出现并且具有合理的默认值。

另一个例子是JSON.stringify。您可能想在space不传递replacer函数的情况下使用参数-然后必须调用…, null, 4)。参数对象可能会更好,尽管它仅对两个参数而言并不合理。


+1与@ trevor-dixon相同的问题:您是否在实践中看到过这种混合用法,例如在js库中?
Christophe

一个例子可能是jQuery的ajax方法。他们接受[强制性] URL作为第一个参数,并接受巨大的options参数作为第二个参数。
Bergi 2012年

太奇怪了!我从来没有注意到这一点。我一直都看到它与url作为选项属性一起使用...
Christophe

是的,jQuery的可选参数确实很奇怪,同时保持了向后兼容:-)
Bergi 2012年

1
我认为,这是这里唯一合理的答案。
本杰明·格伦鲍姆

11

使用“选项作为对象”方法将是最好的。您不必担心属性的顺序,并且可以更灵活地传递哪些数据(例如,可选参数)

创建对象还意味着这些选项可以轻松地用于多种功能:

options={
    nodeList:...,
    callback:...,
    thisObject:...,
    fromIndex:...,
    toIndex:...
}

function1(options){
    alert(options.nodeList);
}

function2(options){
    alert(options.fromIndex);
}

这里(合理的)假设是对象将始终具有相同的密钥对。如果您使用的是废话/不一致的API,那么您会遇到其他问题。
桌'17

9

我认为,如果要实例化某些对象或调用对象的方法,则要使用options对象。如果它是仅对一个或两个参数进行运算并返回值的函数,则最好使用参数列表。

在某些情况下,最好同时使用两者。如果您的函数具有一个或两个必需参数以及一堆可选参数,则使前两个参数为必需参数,并使第三个参数为可选选项哈希。

在您的示例中,我愿意map(nodeList, callback, options)。Nodelist和callback是必需的,仅通过读取对它的调用就可以很容易地判断正在发生的事情,这就像现有的map函数一样。任何其他选项都可以作为可选的第三个参数传递。


+1有趣。您是否看到它在实践中使用过,例如在js库中使用?
Christophe

7

您对以下问题的评论:

在我的示例中,后三个是可选的。

那么为什么不这样做呢? (注意:这是相当原始的Javascript。通常,我会使用default哈希,并使用Object.extendJQuery.extend或类似方法传递的选项对其进行更新。)

function map(nodeList, callback, options) {
   options = options || {};
   var thisObject = options.thisObject || {};
   var fromIndex = options.fromIndex || 0;
   var toIndex = options.toIndex || 0;
}

因此,由于现在更加明显的是可选参数和非可选参数,所有这些都是该函数的有效用法:

map(nodeList, callback);
map(nodeList, callback, {});
map(nodeList, callback, null);
map(nodeList, callback, {
   thisObject: {some: 'object'},
});
map(nodeList, callback, {
   toIndex: 100,
});
map(nodeList, callback, {
   thisObject: {some: 'object'},
   fromIndex: 0,
   toIndex: 100,
});

这类似于@ trevor-dixon的答案。
Christophe

5

我可能对这个响应晚了一点,但是我在这个主题上搜索其他开发人员的意见,并且碰到了这个话题。

我非常不同意大多数回应者,并支持“多种论点”方法。我的主要论点是,它会阻止其他反模式,例如“变异和返回参数对象”,或“将相同的参数对象传递给其他函数”。我曾在广泛滥用此反模式的代码库中工作,而调试能快速执行此操作的代码成为不可能。我认为这是非常特定于Java的经验法则,因为Javascript的类型不是很强,并且允许使用此类任意结构的对象。

我个人的观点是,开发人员在调用函数时应该明确,避免传递多余的数据并避免按引用修改。并不是说这种模式会阻止编写简洁,正确的代码。我只是觉得这使您的项目更容易陷入不良的开发实践中。

考虑以下可怕的代码:

function main() {
    const x = foo({
        param1: "something",
        param2: "something else",
        param3: "more variables"
    });

    return x;
}

function foo(params) {
    params.param1 = "Something new";
    bar(params);
    return params;
}


function bar(params) {
    params.param2 = "Something else entirely";
    const y = baz(params);
    return params.param2;
}

function baz(params) {
    params.params3 = "Changed my mind";
    return params;
}

这种要求不仅需要更明确的文档来指定意图,而且还为模糊的错误留有余地。如果开发人员修改了param1bar()怎么办?您认为浏览足够大小的代码库要花多长时间才能发现此问题?可以肯定的是,这个示例有点虚假,因为它假定此时开发人员已经提交了几种反模式。但是,它显示了包含参数的传递对象如何为错误和歧义提供更大的空间,从而需要更高的责任心和对const正确性的遵守。

关于这个问题,仅我的两分钱!


3

这取决于。

根据我对那些流行的库设计的观察,以下是我们应该使用选项对象的方案:

  • 参数列表很长(> 4)。
  • 部分或全部参数是可选的,它们不依赖特定顺序。
  • 在将来的API更新中,参数列表可能会增加。
  • 该API将从其他代码中调用,并且API名称不够清晰,无法说明参数的含义。因此,可能需要强健的参数名称以提高可读性。

和方案使用参数列表:

  • 参数列表很短(<= 4)。
  • 大部分或所有参数都是必需的。
  • 可选参数按一定顺序排列。(即:$ .get)
  • 易于通过API名称区分参数含义。

2

对象是更可取的,因为如果您传递一个对象,它很容易扩展该对象中的属性数量,并且您不必监视参数传递的顺序。


1

对于通常使用一些预定义参数的函数,最好使用选项对象。相反的示例将是一个函数,该函数将获取无数个参数,例如:setCSS({height:100},{width:200},{background:“#000”})。


0

我会看大型javascript项目。

像谷歌地图这样的事情,您会经常看到实例化的对象需要一个对象,而函数需要参数。我认为这与OPTION argumemnts有关。

如果需要默认参数或可选参数,则对象可能会更好,因为它更灵活。但是,如果不这样做,则正常的函数参数会更明确。

Javascript也有一个arguments对象。 https://developer.mozilla.org/zh-CN/docs/JavaScript/Reference/Functions_and_function_scope/arguments

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.