有没有办法在 JavaScript 的函数调用中提供命名参数?

IT技术 javascript function optional-parameters named-parameters
2021-02-06 18:57:19

我发现 C# 中的命名参数功能在某些情况下非常有用。

calculateBMI(70, height: 175);

如果我想在 JavaScript 中使用它,我可以使用什么?


我不想要的是这样的:

myFunction({ param1: 70, param2: 175 });

function myFunction(params){
  // Check if params is an object
  // Check if the parameters I need are non-null
  // Blah blah
}

我已经使用过这种方法。还有其他方法吗?

我可以使用任何图书馆来做到这一点。

6个回答

ES2015 及更高版本

在 ES2015 中,可以使用参数解构来模拟命名参数。它需要调用者传递一个对象,但如果您还使用默认参数,则可以避免函数内部的所有检查:

myFunction({ param1 : 70, param2 : 175});

function myFunction({param1, param2}={}){
  // ...function body...
}

// Or with defaults, 
function myFunc({
  name = 'Default user',
  age = 'N/A'
}={}) {
  // ...function body...
}

ES5

有一种方法可以接近您想要的结果,但它基于Function.prototype.toString [ES5]的输出,这在某种程度上取决于实现,因此它可能无法跨浏览器兼容。

这个想法是从函数的字符串表示中解析参数名称,以便您可以将对象的属性与相应的参数相关联。

一个函数调用可能看起来像

func(a, b, {someArg: ..., someOtherArg: ...});

其中ab是位置参数,最后一个参数是带有命名参数的对象。

例如:

var parameterfy = (function() {
    var pattern = /function[^(]*\(([^)]*)\)/;

    return function(func) {
        // fails horribly for parameterless functions ;)
        var args = func.toString().match(pattern)[1].split(/,\s*/);

        return function() {
            var named_params = arguments[arguments.length - 1];
            if (typeof named_params === 'object') {
                var params = [].slice.call(arguments, 0, -1);
                if (params.length < args.length) {
                    for (var i = params.length, l = args.length; i < l; i++) {
                        params.push(named_params[args[i]]);
                    }
                    return func.apply(this, params);
                }
            }
            return func.apply(null, arguments);
        };
    };
}());

您将用作:

var foo = parameterfy(function(a, b, c) {
    console.log('a is ' + a, ' | b is ' + b, ' | c is ' + c);     
});

foo(1, 2, 3); // a is 1  | b is 2  | c is 3
foo(1, {b:2, c:3}); // a is 1  | b is 2  | c is 3
foo(1, {c:3}); // a is 1  | b is undefined  | c is 3
foo({a: 1, c:3}); // a is 1  | b is undefined  | c is 3 

演示

这种方法有一些缺点(你已经被警告过!):

  • 如果最后一个参数是一个对象,则将其视为“命名参数对象”
  • 您将始终获得与您在函数中定义的一样多的参数,但其中一些可能具有值undefined(这与根本没有值不同)。这意味着您不能使用arguments.length来测试已经传递了多少参数。

除了让函数创建包装器之外,您还可以拥有一个接受函数和各种值作为参数的函数,例如

call(func, a, b, {posArg: ... });

甚至扩展Function.prototype以便您可以执行以下操作:

foo.execute(a, b, {posArg: ...});
关于undefinedvs “无值”的问题,可以补充一点,这正是 JS 函数的默认值的工作方式——将其undefined视为缺失值。
2021-03-18 18:57:19
非常小的挑剔:我认为这种方法不会捕获 EcmaScript 6 Arrow Functions目前不是一个大问题,但在您的答案的警告部分可能值得一提。
2021-03-24 18:57:19
是的......这是一个例子:jsfiddle.net/9U328/1(尽管你应该使用Object.defineProperty并设置enumerablefalse)。在扩展本机对象时应始终小心。整个方法感觉有点hacky,所以我不希望它现在和永远有效;)
2021-03-26 18:57:19
@Yarin:这个“解决方案”假设如果一个对象作为最后一个参数传递给函数调用,那么该对象包含“命名”参数的值。但是,如果函数自然希望将对象作为参数传递,则该假设不起作用。例如考虑function deleteUser(user) { ... }deleteUser({name: ...})不起作用,因为该对象被解释为“命名参数对象”。你必须写deleteUser({user: {name: ...}}).
2021-03-29 18:57:19
@NobodyMan:没错。我在箭头函数出现之前写了这个答案。在 ES6 中,我实际上会求助于参数解构。
2021-04-09 18:57:19

- 对象方法是 JavaScript 对此的回答。如果您的函数需要一个对象而不是单独的参数,则这没有问题。

2021-04-04 18:57:19
@RobertMaben - 所提出的具体问题的答案是,在不知道声明的变量或函数存在于特定命名空间的情况下,没有本地方法来收集它们。仅仅因为答案很短,它并没有否定它作为答案的适用性——你不同意吗?那里有更短的答案,沿着“不,不可能”的路线。它们可能很短,但它们也是问题的答案。
2021-04-05 18:57:19
这绝对是一个公平的答案——“命名参数”是一种语言特性。在没有这样的特性的情况下,对象方法是下一个最好的选择。
2021-04-07 18:57:19

很多人说只需使用“传递对象”技巧即可命名参数。

/**
 * My Function
 *
 * @param {Object} arg1 Named arguments
 */
function myFunc(arg1) { }

myFunc({ param1 : 70, param2 : 175});

这很好用,除了......当涉及到大多数 IDE 时,我们中的很多开发人员都依赖于我们 IDE 中的类型/参数提示。我个人使用 PHP Storm(以及其他 JetBrains IDE,如 Python 的 PyCharm 和 Objective C 的 AppCode)

使用“传递对象”技巧的最大问题是,当您调用函数时,IDE 会给您一个类型提示,仅此而已……我们应该如何知道哪些参数和类型应该进入arg1 对象?

我不知道 arg1 中应该包含哪些参数

所以......“传递一个对象”技巧对我不起作用......它实际上会导致更多的麻烦,因为在我知道函数期望的参数之前必须查看每个函数的文档块......当然,它非常适合当您维护现有代码时,但编写新代码很糟糕。

嗯,这就是我使用的技术......现在,它可能存在一些问题,一些开发人员可能会告诉我我做错了,而我对这些事情持开放态度......我总是愿意寻找更好的方法来完成任务......所以,如果这个技术有问题,欢迎提出意见。

/**
 * My Function
 *
 * @param {string} arg1 Argument 1
 * @param {string} arg2 Argument 2
 */
function myFunc(arg1, arg2) { }

var arg1, arg2;
myFunc(arg1='Param1', arg2='Param2');

这样,我就拥有了两全其美的优势……新代码很容易编写,因为我的 IDE 为我提供了所有正确的参数提示……而且,在稍后维护代码的同时,我可以一目了然,不仅传递给函数的值,还有参数的名称。我看到的唯一开销是将参数名称声明为局部变量以防止污染全局命名空间。当然,这有点额外的输入,但与在编写新代码或维护现有代码时查找文档块所需的时间相比是微不足道的。

现在,我在创建新代码时拥有所有参数和类型

当一些未来的维护者出现并认为他们可以改变参数顺序(但显然不能)时,这似乎只是在自找麻烦。
2021-03-13 18:57:19
这种技术唯一的问题是你不能改变参数的顺序……不过我个人对此没有意见。
2021-03-18 18:57:19
@AndrewMedico 我同意……看起来您可以像在 Python 中一样更改参数顺序。我唯一能说的是,当改变参数顺序破坏程序时,他们会很快发现。
2021-03-25 18:57:19
我认为这myFunc(/*arg1*/ 'Param1', /*arg2*/ 'Param2');比 更好myFunc(arg1='Param1', arg2='Param2');,因为它没有机会欺骗读者,因为任何实际的命名参数都在发生
2021-03-28 18:57:19
提议的模式与命名参数无关,顺序仍然很重要,名称不需要与实际参数保持同步,命名空间被不必要的变量污染,并且隐含不存在的关系。
2021-03-28 18:57:19

如果你想清楚每个参数是什么,而不是仅仅调用

someFunction(70, 115);

为什么不做以下事情

var width = 70, height = 115;
someFunction(width, height);

当然,这是额外的一行代码,但它在可读性方面胜出。

+1 遵循 KISS 原则,此外它还有助于调试。但是,我认为每个 var 都应该在自己的行上,尽管性能受到轻微影响(http://stackoverflow.com/questions/9672635/javascript-var-statement-and-performance)。
2021-03-26 18:57:19
这不仅与额外的代码行有关,还与参数的顺序有关并使它们可选。所以你可以用命名参数来写这个:someFunction(height: 115);,但如果你写,someFunction(height);你实际上是在设置宽度。
2021-03-28 18:57:19
在这种情况下,CoffeeScript 支持命名参数。它会让你写只是someFunction(width = 70, height = 115);变量在生成的 JavaScript 代码中的当前作用域的顶部声明。
2021-04-01 18:57:19

另一种方法是使用合适对象的属性,例如:

function plus(a,b) { return a+b; };

Plus = { a: function(x) { return { b: function(y) { return plus(x,y) }}}, 
         b: function(y) { return { a: function(x) { return plus(x,y) }}}};

sum = Plus.a(3).b(5);

当然,对于这个编造的例子来说,它有点没有意义。但是在函数看起来像的情况下

do_something(some_connection_handle, some_context_parameter, some_value)

它可能更有用。它还可以与“parameterfy”想法相结合,以通用方式从现有函数中创建这样的对象。也就是说,对于每个参数,它将创建一个成员,该成员可以评估为函数的部分评估版本。

这个想法当然与 Schönfinkeling aka Currying 有关。

这是一个很好的主意,如果将它与参数内省技巧结合起来,效果会更好。不幸的是,它无法使用可选参数
2021-04-03 18:57:19