332

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

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

我可以使用任何库来执行此操作。

4

12 回答 12

346

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: ...});
于 2012-08-03T13:32:16.777 回答
85

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

于 2012-08-03T12:54:44.687 回答
35

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

/**
 * 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 为我提供了所有正确的参数提示...而且,在以后维护代码时,我可以一目了然,不仅传递给函数的值,也是参数的名称。我看到的唯一开销是将您的参数名称声明为局部变量,以防止污染全局命名空间。当然,这有点额外的输入,但与在编写新代码或维护现有代码时查找文档块所需的时间相比,这是微不足道的。

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

于 2014-07-10T20:06:24.677 回答
26

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

someFunction(70, 115);

为什么不做以下

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

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

于 2014-06-03T13:29:59.273 回答
7

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

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 有关。

于 2013-02-07T15:40:38.117 回答
3

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}))

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

例如,您可能希望在函数定义中缩写heightwidthas hw以使其更短、更清晰,同时为了清楚起见,您仍希望使用全名来调用它:

// 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})

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

于 2019-08-25T03:11:21.957 回答
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));
于 2015-05-02T23:43:58.413 回答
1

这无疑是伪代码,但我相信它会起作用(我知道它在 Typescript 中起作用;我正在将它用于 JavaScript)

// Target Function
const myFunc = (a=1,b=2,c=3) => {a+b+c}

// Goal usage:
myFunc(a=5, b=6) // 14
myFunc(c=0) // 3

// set your defaults
const myFuncDefaults = {a:1, b:2, c:3};

// override them with passed params
const myFuncParams = (params) => { return Object.assign(myFuncDefaults, params)}


// use the overloaded dict as the input
const myFunc2 = (params) => {
  let {a, b, c} = myFuncParams(params);
  return myFunc(a, b, c)
}

// Usage:
myFunc({a:5, b:6}) // 14
myFunc({c:0}) // 3

// Written more succinctly:
const myFunc = (params) => {
  let {a,b,c} = Object.assign({a:1, b:2, c:3}, params)
  return a + b + c
}

FWIW Typescript 通过提示使这种方式变得很好:

interface IParams {
  a: number;
  b: number;
  c: number;
}

const myFunc = (params: Partial<IParams>): number => {
  const default: IParams = {a:1, b:2, c:3};
  let {a, b, c} = Object.assign(default, params)
  return a + b + c
}
于 2021-06-11T06:25:17.140 回答
1

注意。如评论中所述,我对 2016 年的回答不正确且具有误导性。

尝试 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
于 2016-08-18T08:58:06.727 回答
0

的,嗯,有点。我找到了 2 个解决方案。我只解释一个。

在这个解决方案中,我们放弃了位置参数。

我们可以使用一个对象(几乎与python 中的 dict相同)来传递参数。

在此示例中,我使用该函数生成图像文件的名称

//first we define our function with just ONE argument
function name_of_img(img_desc){

  // with this step, any undefined value will be assigned a value
  if(img_desc.size == undefined) {img_desc.size = "400x500"}
  if(img_desc.format == undefined) {img_desc.format = ".png"}

  console.log(img_desc.size + img_desc.format)
}

//notice inside our function we're passing a dict/object
    
name_of_img({size: "200x250", format=".jpg"})
// returns "200x250.jpg"

name_of_img({size: "1200x950"})
// returns "1200x950.png"

我们可以修改这个例子,所以我们也可以使用位置参数,我们也可以修改它,所以可以传递无效的参数,我想我会为此做一个 github 存储库

于 2021-10-02T09:58:07.257 回答
0

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

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

于 2019-08-02T01:06:08.453 回答
-3

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

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

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

注意事项:

  • 必须保留参数的顺序 - 但该模式仍然有用,因为它使哪个实际参数适用于哪个形式参数很明显,而无需 grep 函数签名或使用 IDE。

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

于 2019-08-05T11:38:20.043 回答