在javascript中命名参数

时间:2012-08-03 12:51:22

标签: javascript function optional-parameters named-parameters

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

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

我可以使用任何库来做这件事。 (或者有人可以指出我已经做过的事情)

11 个答案:

答案 0 :(得分:137)

ES2015及更高版本

在ES2015中,parameter destructuring可用于模拟命名参数。它需要调用者传递一个对象,但如果你也使用默认参数,你可以避免函数内部的所有检查:

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 

DEMO

此方法存在一些缺点(您已收到警告!):

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

您可以使用一个函数来接受函数和各种值作为参数,而不是创建包装器的函数,例如

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

或甚至扩展Function.prototype以便您可以:

foo.execute(a, b, {posArg: ...});

答案 1 :(得分:58)

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

答案 2 :(得分:22)

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

很多人说只是使用“传递一个对象”技巧,以便你有命名参数。

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

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

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

使用“传递对象”技巧的最大问题是,当您调用该函数时,IDE会为您提供单一类型提示,就是这样......我们应该知道哪些参数和类型应该进入arg1对象?

I have no idea what parameters should go in 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所花费的时间相比微不足道。

Now, I have all the parameters and types when creating new code

答案 3 :(得分:17)

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

someFunction(70, 115);

为什么不做以下

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

当然,这是一个额外的代码行,但它在可读性上获胜。

答案 4 :(得分:5)

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

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

答案 5 :(得分:3)

还有另一种方式。如果您通过引用传递对象,则该对象的属性将显示在函数的本地范围中。我知道这适用于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));

答案 6 :(得分:1)

尝试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

答案 7 :(得分:0)

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

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

答案 8 :(得分:0)

以命名参数作为对象传递的调用函数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}))

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

例如,您可能想将heightwidth缩写为函数定义内的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})

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

答案 9 :(得分:0)

这确实是 sudo 代码,但我相信它会起作用(我知道它在 Typescript 中起作用;我正在将它用于 js)

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

答案 10 :(得分:-1)

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

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

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

注意事项:

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

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