【问题标题】: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,不应该让任何半经验的编码员感到困惑。

标签: javascript json function object arguments


【解决方案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)。

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

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

      在你的情况下,我推荐map(nodeList, callback, options)nodelistcallback 是必需的,其他三个参数只是偶尔出现并且具有合理的默认值。

      另一个例子是JSON.stringify。您可能想使用space 参数而不传递replacer 函数——那么您必须调用…, null, 4)。一个 arguments 对象可能会更好,尽管它仅适用于 2 个参数并不合理。

      【讨论】:

      • +1 与@trevor-dixon 相同的问题:您是否在实践中看到过这种混合使用,例如在 js 库中?
      • 一个例子可以是 jQuery 的ajax methods。他们接受 [强制] URL 作为第一个参数,并接受一个巨大的选项参数作为第二个参数。
      • 太奇怪了!我以前从未注意到这一点。我一直看到它与 url 一起用作选项属性...
      • 是的,jQuery 用它的可选参数做了一些奇怪的事情,同时保持向后兼容:-)
      • 在我看来,这是这里唯一理智的答案。
      【解决方案6】:

      您对该问题的评论:

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

      那么为什么不这样做呢? (注意:这是相当原始的 Javascript。通常我会使用 default 哈希并使用 Object.extendJQuery.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 更可取,因为如果您传递一个对象,它很容易扩展该对象中的属性数量,并且您不必注意传递参数的顺序。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2011-06-05
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-10-10
            • 1970-01-01
            相关资源
            最近更新 更多