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


207

我发现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
}

我已经使用过这种方法。还有另一种方法吗?

我可以使用任何库来做到这一点。


我认为这是不可能的,但是您可以尝试将一些未定义的内容放到空的地方。真不好 使用对象,它的好处。
弗拉迪斯拉夫·奎林2012年

14
不,JavaScript / EcmaScript不支持命名参数。抱歉。
smilly92,2012年

1
我已经知道了 谢谢。我一直在寻找涉及调整Functionjavascript中现有功能的方法。
罗宾·马本

1
FunctionJavascript中现有的内容无法更改Javascript的核心语法
Gareth 2012年

2
我认为Javascript不支持此功能。我认为最接近命名参数的是(1)添加注释calculateBMI(70, /*height:*/ 175);,(2)提供对象calculateBMI(70, {height: 175}),或(3)使用常量const height = 175; calculateBMI(70, height);
tfmontague

Answers:


211

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: ...});

是的...这是一个示例:jsfiddle.net/9U328/1(尽管您应该使用Object.defineProperty并设置enumerablefalse)。扩展本机对象时,应始终小心。整个方法感觉有些棘手,所以我不希望它现在就永远有效;)
Felix Kling 2012年

注意。我将开始使用它。标记为答案!...
暂时

1
非常小的鸡蛋里挑骨头:我不认为这种做法将捕获的EcmaScript 6 箭功能。目前还不是一个大问题,但可能需要在答案的警告部分中提及。
NobodyMan 2015年

3
@ NobodyMan:是的。我在箭头功能还不是东西之前就写了这个答案。在ES6中,我实际上将诉诸参数解构。
Felix Kling

1
关于undefinedvs“无值”的问题,可以添加的是,这正是JS函数默认值的工作方式- undefined视为缺失值。
德米特里·扎伊采夫

71

-对象方法是JavaScript的答案。只要您的函数期望一个对象而不是单独的参数,那么这没有问题。


35
@RobertMaben-所提出的特定问题的答案是,在不知道声明的var或函数存在特定命名空间的情况下,没有本机的方法可以收集它们。仅仅因为答案很短,它并不会否定它作为答案的适用性-您不同意吗?那里的答案要短得多,类似“不可能,不可能”。简而言之,也许是答案。
Mitya


1
这绝对是一个合理的答案-“命名参数”是一种语言功能。在没有这种功能的情况下,对象方法是次之。
fatuhoku

32

这个问题在一段时间以来一直是我的宠儿。我是一位经验丰富的程序员,会讲多种语言。我最喜欢使用的一种语言是Python。Python支持命名参数,没有任何麻烦。...自从我开始使用Python(前一段时间)以来,一切都变得更加容易。我相信每种语言都应支持命名参数,但事实并非如此。

很多人说只使用“传递对象”技巧,以便您命名参数。

/**
 * 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中使用什么参数

所以...“传递对象”的技巧对我不起作用...实际上,在我不知道该函数需要什么参数之前,不得不先查看每个函数的docblock,这会引起更多的麻烦。.当然,对于当您维护现有代码时,但是编写新代码太可怕了。

好吧,这是我使用的技术。...现在,它可能存在一些问题,一些开发人员可能会告诉我我做错了,在这些事情上我持开放的态度...我一直乐于寻找完成任务的更好方法。因此,如果此技术存在问题,欢迎发表评论。

/**
 * 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为我提供了所有适当的参数提示...而且,尽管以后再维护代码,我一眼就能看到,不仅传递给函数的值,还有参数的名称。我看到的唯一开销是将参数名称声明为局部变量,以免污染全局名称空间。当然,这是额外的输入,但是与编写新代码或维护现有代码时查找docblock所花费的时间相比是微不足道的。

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


2
这种技术唯一的事实是您无法更改参数的顺序……不过我个人对此很满意。
Ray Perea 2014年

13
似乎这只是在将来的一些维护者出现时提出麻烦,并认为他们可以更改参数顺序(但显然不能)。
没人

1
@AndrewMedico我同意...看起来确实可以像在Python中一样更改参数顺序。我唯一能说的是,当改变参数顺序使程序中断时,它们会很快发现。
Ray Perea 2014年

7
我认为这myFunc(/*arg1*/ 'Param1', /*arg2*/ 'Param2');比更好myFunc(arg1='Param1', arg2='Param2');,因为与任何实际的具名论点相比,这没有欺骗读者的机会
Eric

2
提出的模式与命名参数无关,顺序仍然很重要,名称不需要与实际参数保持同步,名称空间被不必要的变量污染,并且隐含了不存在的关系。
德米特里·扎伊采夫

25

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

someFunction(70, 115);

为什么不这样做以下

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

当然,这是额外的代码行,但是在可读性上胜出。


5
+1用于遵循KISS原则,此外它还有助于调试。但是,我认为每个var应该在自己的行上,尽管会对性能造成轻微影响(http://stackoverflow.com/questions/9672635/javascript-var-statement-and-performance)。
clairestreb 2014年

21
这不仅涉及额外的代码行,还涉及参数的顺序并使它们可选。因此,您可以使用命名参数:编写此代码someFunction(height: 115);,但是如果您编写此代码someFunction(height);,则实际上是在设置宽度。
Francisco Presencia,2015年

在这种情况下,CoffeeScript支持命名参数。它可以让您写得恰到好处someFunction(width = 70, height = 115);。在生成的JavaScript代码中,变量在当前作用域的顶部声明。
奥登·奥尔森

6

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

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又名Currying有关。


这是一个很好的主意,如果将其与自省技巧结合使用,它会变得更好。不幸的是,它无法使用可选参数
Eric

2

还有另一种方式。如果要通过引用传递对象,则该对象的属性将出现在函数的本地范围内。我知道这适用于Safari(尚未检查其他浏览器),并且我不知道此功能是否有名称,但是以下示例说明了其用法。

尽管在实践中,我认为除了您已经使用的技术之外,它不提供任何功能价值,但从语义上讲它要干净一些。而且它仍然需要传递对象引用或对象文字。

function sum({ a:a, b:b}) {
    console.log(a+'+'+b);
    if(a==undefined) a=0;
    if(b==undefined) b=0;
    return (a+b);
}

// will work (returns 9 and 3 respectively)
console.log(sum({a:4,b:5}));
console.log(sum({a:3}));

// will not work (returns 0)
console.log(sum(4,5));
console.log(sum(4));

2

尝试使用Node-6.4.0(process.versions.v8 ='5.0.71.60')和Node Chakracore-v7.0.0-pre8,然后再使用Chrome-52(V8 = 5.2.361.49),我注意到命名参数几乎是已实施,但该顺序仍然优先。我找不到ECMA标准所说的内容。

>function f(a=1, b=2){ console.log(`a=${a} + b=${b} = ${a+b}`) }

> f()
a=1 + b=2 = 3
> f(a=5)
a=5 + b=2 = 7
> f(a=7, b=10)
a=7 + b=10 = 17

但是需要订购!!这是标准行为吗?

> f(b=10)
a=10 + b=2 = 12

10
那不是你想的那样。b=10表达式的结果是10,这就是传递给函数的内容。f(bla=10)也将起作用(它将10分配给变量,bla然后将值传递给函数)。
Matthias Kestenholz

1
这也是在全局范围内创建ab变量的副作用。
Gershom '18年

2

f以命名参数作为对象传递的调用函数

o = {height: 1, width: 5, ...}

基本上是f(...g(o))在我使用传播语法的地方调用它的组成,并且g是将对象值与其参数位置连接起来的“绑定”映射。

绑定映射恰好是缺少的成分,可以通过其键数组来表示:

// map 'height' to the first and 'width' to the second param
binding = ['height', 'width']

// take binding and arg object and return aray of args
withNamed = (bnd, o) => bnd.map(param => o[param])

// call f with named args via binding
f(...withNamed(binding, {hight: 1, width: 5}))

请注意三个分离的成分:函数,具有命名参数的对象和绑定。这种解耦为使用此结构提供了很大的灵活性,其中绑定可以在函数的定义中任意自定义,并在函数调用时任意扩展。

例如,你可能要缩写height,并widthhw你的函数的定义中,使其更短和更清洁的,而你还是要为清楚起见以全名来称呼它:

// use short params
f = (h, w) => ...

// modify f to be called with named args
ff = o => f(...withNamed(['height', 'width'], o))

// now call with real more descriptive names
ff({height: 1, width: 5})

这种灵活性对于函数式编程也更为有用,在函数式编程中,函数可以随其原始参数名称丢失而随意转换。


0

来自Python,这困扰了我。我为节点编写了一个简单的包装器/代理,它将接受位置和关键字对象。

https://github.com/vinces1979/node-def/blob/master/README.md


如果我理解正确,那么您的解决方案需要在函数的定义中区分位置参数和命名参数,这意味着我没有自由在调用时做出选择。
德米特里·扎伊采夫

@DmitriZaitsev:在Python社区(关键字参数是日常功能)中,我们认为能够强制用户通过关键字指定可选参数是一个很好的主意;这有助于避免错误。
Tobias

@Tobias强制用户在JS中这样做是一个已解决的问题:f(x, opt),哪里opt是对象。这是否有助于避免或造成错误(例如由拼写错误和记住关键字名称引起的痛苦)仍然是一个问题。
德米特里·扎伊采夫

@DmitriZaitsev:在Python中,这严格避免了错误,因为(当然)关键字参数是那里的核心语言功能。对于关键字的参数,Python 3具有特殊的语法(而在Python 2中,您将一一弹出kwargs dict中的键,TypeError如果还剩下未知键,则最后引发a )。您的f(x, opt)解决方案允许f功能做类似的东西在Python 2,但你需要自己处理所有的默认值。
托比亚斯

@Tobias这个建议与JS有关吗?它似乎描述了如何f(x, opt)工作,而我看不到如何帮助回答这个问题,例如,您想同时做这两个事情,request(url)request({url: url})通过创建url仅关键字的参数则不可能。
德米特里·扎伊采夫

-1

与通常认为的相反,可以通过简单,简洁的编码约定在标准的老式JavaScript(仅适用于布尔参数)中实现命名参数,如下所示。

function f(p1=true, p2=false) {
    ...
}

f(!!"p1"==false, !!"p2"==true); // call f(p1=false, p2=true)

注意事项:

  • 必须保留参数的顺序-但是该模式仍然有用,因为它可以清楚地表明哪个实际参数是针对哪个形式参数的,而无需grep获取功能签名或使用IDE。

  • 这仅适用于布尔值。但是,我确信可以使用JavaScript的独特类型强制语义为其他类型开发类似的模式。


您仍按职位而不是这里的名字指称。
德米特里·扎伊采夫

@DmitriZaitsev是的,我在上面也这么说。但是,命名参数的目的是使读者清楚地知道每个参数的含义。这是一种文档形式。我的解决方案使文档可以嵌入函数调用中,而无需诉诸于看上去很乱的注释。
user234461 '19

这解决了另一个问题。问题是关于height不管参数顺序如何都按名称传递。
德米特里·扎伊采夫

@DmitriZaitsev实际上,问题没有说任何关于参数顺序的问题,或者说OP为什么要使用命名的参数。
user234461 '19

它是通过引用C#来实现的,其中以任何顺序按名称传递参数是关键。
德米特里·扎伊采夫
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.