多个参数与选项对象
Posted
技术标签:
【中文标题】多个参数与选项对象【英文标题】:Multiple arguments vs. options object 【发布时间】:2012-10-01 08:59:51 【问题描述】:在创建具有多个参数的 javascript 函数时,我总是面临这样的选择:传递参数列表与传递选项对象。
例如,我正在编写一个将 nodeList 映射到数组的函数:
function map(nodeList, callback, thisObject, fromIndex, toIndex)
...
我可以改用这个:
function map(options)
...
其中 options 是一个对象:
options=
nodeList:...,
callback:...,
thisObject:...,
fromIndex:...,
toIndex:...
推荐的方式是哪一种?是否有关于何时使用一种与另一种的指南?
[更新] 似乎有一个支持选项对象的共识,所以我想添加一条评论:在我的案例中,我很想使用参数列表的一个原因是行为一致使用 JavaScript 内置的 array.map 方法。
【问题讨论】:
第二个选项给你命名参数,在我看来这是一件好事。 它们是可选参数还是必需参数? @user1689607 在我的示例中,最后三个是可选的。 因为您的最后两个参数非常相似,如果用户只传递了一个或另一个,您将永远无法真正知道是哪一个。因此,您几乎需要命名参数。但我可以理解您希望维护一个类似于本机 API 的 API。 如果你的函数做了类似的事情,在原生 API 之后建模并不是一件坏事。这一切都归结为“是什么让代码最易读”。Array.prototype.map
有一个简单的 API,不应该让任何半经验的编码员感到困惑。
【参考方案1】:
两者都使用会很好。如果您的函数有一个或两个必需参数和一堆可选参数,则将前两个参数设为必需参数,将第三个参数设为可选选项哈希。
在你的例子中,我会做map(nodeList, callback, options)
。节点列表和回调是必需的,通过读取对它的调用很容易知道发生了什么,它就像现有的地图函数一样。任何其他选项都可以作为可选的第三个参数传递。
【讨论】:
+1 有趣。你见过它在实践中的使用吗,例如在 js 库中?【参考方案2】:我的回复可能有点晚了,但我正在寻找其他开发者对这个话题的看法,结果发现了这个帖子。
我非常不同意大多数响应者的观点,并支持“多重论点”的方法。我的主要论点是它不鼓励其他反模式,例如“变异并返回参数对象”或“将相同的参数对象传递给其他函数”。我曾在广泛滥用这种反模式的代码库中工作,并且很快就无法调试执行此操作的代码。我认为这是一个非常特定于 Javascript 的经验法则,因为 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;
这种类型不仅需要更明确的文档来指定意图,而且还为模糊错误留下了空间。
如果开发人员在bar()
中修改param1
怎么办?您认为查看足够大小的代码库需要多长时间才能捕捉到这一点?
诚然,这个例子有点虚伪,因为它假设开发人员此时已经提交了几个反模式。但它显示了传递包含参数的对象如何允许更大的错误和歧义空间,需要更大程度的责任心和对 const 正确性的遵守。
在这个问题上只是我的两分钱!
【讨论】:
【参考方案3】:与许多其他人一样,我通常更喜欢将 options object
传递给函数,而不是传递一长串参数,但这实际上取决于确切的上下文。
我使用代码可读性作为试金石。
例如,如果我有这个函数调用:
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
);
它更像是一门艺术而不是一门科学,但如果我不得不说出经验法则:
在以下情况下使用选项参数:
您有四个以上的参数 任何参数都是可选的 您曾经不得不查找函数来确定它需要哪些参数 如果有人在尖叫“ARRRRRG!”时试图勒死你【讨论】:
很好的答案。这取决于。当心布尔陷阱ariya.ofilabs.com/2011/08/hall-of-api-shame-boolean-trap.html 哦,是的...我忘记了那个链接。这真的让我重新思考 API 是如何工作的,甚至在得知自己做的事情很愚蠢后,我什至重写了几段代码。谢谢! '你曾经不得不查找函数来弄清楚它需要什么参数' - 使用选项对象代替,你是否总是必须查找确定需要密钥及其名称的方法。 IDE 中的 Intellisense 不会显示此信息,而 params 会显示此信息。在大多数 IDE 中,您只需将鼠标悬停在方法上,它就会显示参数是什么。 如果你们都对这种“编码最佳实践”的性能影响感兴趣,这里有一个 jsPerf 测试两种方式:jsperf.com/function-boolean-arguments-vs-options-object/10 请注意第三种选择中的小扭曲,我使用一个“预设”(常量)选项对象,可以在您使用相同设置进行多次调用(在运行时的生命周期内,例如您的网页)时完成,在开发时已知(简而言之:当您的选项值是有点硬编码在你的源代码中)。 @Sean 老实说,我根本不再使用这种编码风格。我已经切换到 TypeScript 并使用命名参数。【参考方案4】:这取决于。
根据我对那些流行的库设计的观察,以下是我们应该使用选项对象的场景:
参数列表很长 (>4)。 部分或全部参数是可选的,它们不依赖于特定的 订购。 参数列表可能会在未来的 API 更新中增加。 API会被其他代码调用,API名称不清楚 足以说明参数的含义。所以它可能需要强大的 便于阅读的参数名称。以及使用参数列表的场景:
参数列表很短( 大部分或所有参数都是必需的。 可选参数按一定顺序排列。 (即:$.get) 很容易通过 API 名称来判断参数的含义。【讨论】:
【参考方案5】:多个参数主要用于强制参数。他们没有任何问题。
如果你有可选参数,它会变得复杂。如果其中一个依赖于其他,因此它们具有一定的顺序(例如第四个需要第三个),您仍然应该使用多个参数。几乎所有原生的 EcmaScript 和 DOM 方法都是这样工作的。一个很好的例子是 open
method of XMLHTTPrequests,其中最后 3 个参数是可选的 - 规则就像“没有用户就没有密码”(另请参阅 MDN docs)。
选项对象在两种情况下派上用场:
您的参数太多以至于令人困惑:“命名”会为您提供帮助,您不必担心它们的顺序(尤其是它们可能会更改时) 您有可选参数。这些对象非常灵活,无需任何排序,您只需传递您需要的东西即可(或undefined
s)。
在你的情况下,我推荐map(nodeList, callback, options)
。 nodelist
和 callback
是必需的,其他三个参数只是偶尔出现并且具有合理的默认值。
另一个例子是JSON.stringify
。您可能想使用space
参数而不传递replacer
函数——那么您必须调用…, null, 4)
。一个 arguments 对象可能会更好,尽管它仅适用于 2 个参数并不合理。
【讨论】:
+1 与@trevor-dixon 相同的问题:您是否在实践中看到过这种混合使用,例如在 js 库中? 一个例子可以是 jQuery 的ajax methods。他们接受 [强制] URL 作为第一个参数,并接受一个巨大的选项参数作为第二个参数。 太奇怪了!我以前从未注意到这一点。我一直看到它与 url 一起用作选项属性... 是的,jQuery 用它的可选参数做了一些奇怪的事情,同时保持向后兼容:-) 在我看来,这是这里唯一理智的答案。【参考方案6】:您对该问题的评论:
在我的示例中,最后三个是可选的。
那么为什么不这样做呢? (注意:这是相当原始的 Javascript。通常我会使用 default
哈希并使用 Object.extend 或 JQuery.extend 或类似方法传入的选项来更新它。) p>
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 的回答。【参考方案7】:对于通常使用一些预定义参数的函数,您最好使用选项对象。相反的例子就像一个函数,它得到无限数量的参数,例如:setCSS(height:100,width:200,background:"#000")。
【讨论】:
【参考方案8】:最好使用“选项作为对象”方法。您不必担心属性的顺序,并且可以更灵活地传递哪些数据(例如可选参数)
创建一个对象还意味着这些选项可以很容易地用于多个功能:
options=
nodeList:...,
callback:...,
thisObject:...,
fromIndex:...,
toIndex:...
function1(options)
alert(options.nodeList);
function2(options)
alert(options.fromIndex);
【讨论】:
这里的(合理的)假设是对象将始终具有相同的密钥对。如果您使用的是垃圾/不一致的 API,那么您将面临不同的问题。【参考方案9】:我会看大型 javascript 项目。
你会经常看到像谷歌地图这样的东西,实例化的对象需要一个对象,但函数需要参数。我认为这与 OPTION 参数有关。
如果您需要默认参数或可选参数,对象可能会更好,因为它更灵活。但是如果你不正常的函数参数更明确。
Javascript 也有一个 arguments
对象。
https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/arguments
【讨论】:
【参考方案10】:Object 更可取,因为如果您传递一个对象,它很容易扩展该对象中的属性数量,并且您不必注意传递参数的顺序。
【讨论】:
以上是关于多个参数与选项对象的主要内容,如果未能解决你的问题,请参考以下文章