¿Hay alguna manera de proporcionar parámetros con nombre en una llamada de función en JavaScript?

207

Encuentro la función de parámetros nombrados en C # bastante útil en algunos casos.

calculateBMI(70, height: 175);

¿Qué puedo usar si quiero esto en JavaScript?


Lo que no quiero es esto:

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
}

Ese enfoque ya lo he usado. ¿Hay otra manera?

Estoy bien usando cualquier biblioteca para hacer esto.

Robin Maben
fuente
No creo que esto sea posible, pero puedes intentar poner algunos indefinidos en lugares vacíos. Lo cual es muy malo. Usa el objeto, es bueno.
Vladislav Qulin
14
No, JavaScript / EcmaScript no admiten parámetros con nombre. Lo siento.
smilly92
1
Eso ya lo se. Gracias. Estaba buscando alguna forma que implique ajustar lo que Functionpuede hacer el JavaScript existente .
Robin Maben
1
Lo existente Functionen Javascript no puede cambiar la sintaxis central de Javascript
Gareth
2
No creo que Javascript admita esta función. Creo que lo más cercano a los parámetros nombrados es (1) agregar un comentario calculateBMI(70, /*height:*/ 175);, (2) proporcionar un objeto calculateBMI(70, {height: 175})o (3) usar una constante const height = 175; calculateBMI(70, height);.
tfmontague

Respuestas:

213

ES2015 y posterior

En ES2015, la desestructuración de parámetros se puede utilizar para simular parámetros con nombre. Requeriría que la persona que llama pasara un objeto, pero puede evitar todas las comprobaciones dentro de la función si también utiliza parámetros predeterminados:

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

Hay una manera de acercarse a lo que desea, pero se basa en la salida de Function.prototype.toString [ES5] , que depende en cierta medida de la implementación, por lo que podría no ser compatible con varios navegadores.

La idea es analizar los nombres de los parámetros de la representación de cadena de la función para que pueda asociar las propiedades de un objeto con el parámetro correspondiente.

Una llamada a la función podría verse así

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

donde ay bson argumentos posicionales y el último argumento es un objeto con argumentos nombrados.

Por ejemplo:

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

Que usarías como:

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 

MANIFESTACIÓN

Hay algunos inconvenientes en este enfoque (¡ya te lo advertimos!):

  • Si el último argumento es un objeto, se trata como un "objeto de argumento con nombre"
  • Siempre obtendrá tantos argumentos como haya definido en la función, pero algunos de ellos pueden tener el valor undefined(eso es diferente de no tener ningún valor). Eso significa que no puede usar arguments.lengthpara probar cuántos argumentos se han pasado.

En lugar de tener una función que cree el contenedor, también podría tener una función que acepte una función y varios valores como argumentos, como

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

o incluso extender Function.prototypepara que puedas hacer:

foo.execute(a, b, {posArg: ...});
Felix Kling
fuente
Sí ... He aquí un ejemplo de ello: jsfiddle.net/9U328/1 (aunque debe utilizar en lugar Object.definePropertyy conjunto enumerablea false). Siempre se debe tener cuidado al extender objetos nativos. Todo el enfoque se siente un poco extraño, por lo que no esperaría que funcione ahora y para siempre;)
Felix Kling
Célebre. Me pondré a usar esto. Marcado como respuesta! ... Por ahora;)
Robin Maben
1
Nitpick muy menor: no creo que este enfoque atrape las funciones de flecha de EcmaScript 6 . Actualmente no es una gran preocupación, pero vale la pena mencionarlo en la sección de advertencias de su respuesta.
NobodyMan
3
@NobodyMan: Cierto. Escribí esta respuesta antes de que las funciones de flecha fueran una cosa. En ES6, realmente recurriría a la desestructuración de parámetros.
Felix Kling
1
En el tema de undefinedvs "sin valor", se puede agregar que así es exactamente como funcionan los valores predeterminados de las funciones de JS, tratando undefinedcomo un valor perdido.
Dmitri Zaitsev
71

No , el enfoque de objeto es la respuesta de JavaScript a esto. No hay ningún problema con esto, siempre que su función espere un objeto en lugar de parámetros separados.

Mitia
fuente
35
@RobertMaben: la respuesta a la pregunta específica que se hace es que no hay una forma nativa de recopilar vars o funciones declaradas sin saber que viven en un espacio de nombres particular. Solo porque la respuesta es corta, no niega su idoneidad como respuesta, ¿no estaría de acuerdo? Hay respuestas mucho más cortas por ahí, en la línea de "no, no t posible". Pueden ser cortos, pero también son la respuesta a la pregunta.
Mitya
3
Sin embargo, hoy en día hay es6: 2ality.com/2011/11/keyword-parameters.html
slacktracer
1
Esta es definitivamente una respuesta justa: los "parámetros con nombre" son una característica del lenguaje. El enfoque de objeto es la siguiente mejor opción en ausencia de tal característica.
fatuhoku el
32

Este problema ha sido una manía mía desde hace algún tiempo. Soy un programador experimentado con muchos idiomas en mi haber. Uno de mis idiomas favoritos que he tenido el placer de usar es Python. Python admite parámetros con nombre sin ningún truco ... Desde que comencé a usar Python (hace algún tiempo) todo se volvió más fácil. Creo que cada idioma debe admitir parámetros con nombre, pero ese no es el caso.

Mucha gente dice que use el truco "Pasar un objeto" para que haya nombrado los parámetros.

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

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

Y eso funciona muy bien, excepto ... cuando se trata de la mayoría de los IDE, muchos de nosotros los desarrolladores confiamos en sugerencias de tipo / argumento dentro de nuestro IDE. Yo personalmente uso PHP Storm (junto con otros IDE de JetBrains como PyCharm para python y AppCode para Objective C)

Y el mayor problema con el uso del truco "Pasar un objeto" es que cuando se llama a la función, el IDE le da una pista de tipo único y eso es todo ... ¿Cómo se supone que debemos saber qué parámetros y tipos deben entrar en el objeto arg1?

No tengo idea de qué parámetros deben ir en arg1

Entonces ... el truco "Pasar un objeto" no funciona para mí ... En realidad, causa más dolores de cabeza al tener que mirar el bloque de documentos de cada función antes de saber qué parámetros espera la función ... Claro, es genial para cuando mantiene el código existente, pero es horrible para escribir código nuevo.

Bueno, esta es la técnica que uso ... Ahora, puede haber algunos problemas con ella, y algunos desarrolladores pueden decirme que lo estoy haciendo mal, y tengo una mente abierta cuando se trata de estas cosas ... Siempre estoy dispuesto a buscar mejores formas de lograr una tarea ... Entonces, si hay un problema con esta técnica, entonces los comentarios son bienvenidos.

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

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

De esta manera, tengo lo mejor de ambos mundos ... el nuevo código es fácil de escribir ya que mi IDE me da todas las sugerencias de argumento adecuadas ... Y, mientras mantengo el código más adelante, puedo ver de un vistazo, no solo el valor pasado a la función, pero también el nombre del argumento. La única sobrecarga que veo es declarar los nombres de sus argumentos como variables locales para evitar contaminar el espacio de nombres global. Claro, es un poco de tipeo adicional, pero trivial en comparación con el tiempo que lleva buscar docblocks mientras escribe un código nuevo o mantiene el código existente.

Ahora, tengo todos los parámetros y tipos al crear un nuevo código

Ray Perea
fuente
2
Lo único con esta técnica es el hecho de que no puede cambiar el orden de los parámetros ... Aunque personalmente estoy de acuerdo con eso.
Ray Perea
14
Parece que esto solo está buscando problemas cuando algún futuro mantenedor aparece y piensa que pueden cambiar el orden de los argumentos (pero obviamente no pueden).
nadie
1
@ AndrewMedico Estoy de acuerdo ... parece que puedes cambiar el orden de los argumentos como en Python. Lo único que puedo decir al respecto es que se darán cuenta rápidamente cuando cambiar el orden de los argumentos rompe el programa.
Ray Perea
77
Yo diría que myFunc(/*arg1*/ 'Param1', /*arg2*/ 'Param2');es mejor que myFunc(arg1='Param1', arg2='Param2');eso, porque eso no tiene ninguna posibilidad de engañar al lector que cualquier argumento con nombre real está sucediendo
Eric
3
El patrón propuesto no tiene nada que ver con argumentos nombrados, el orden sigue siendo importante, los nombres no necesitan estar sincronizados con los parámetros reales, el espacio de nombres está contaminado con variables innecesarias y las relaciones están implícitas y no están ahí.
Dmitri Zaitsev
25

Si desea dejar en claro cuáles son cada uno de los parámetros, en lugar de simplemente llamar

someFunction(70, 115);

por qué no hacer lo siguiente

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

claro, es una línea de código extra, pero gana en legibilidad.

dav_i
fuente
55
+1 por seguir el principio KISS, además de que también ayuda con la depuración. Sin embargo, creo que cada var debería estar en su propia línea, aunque con un ligero impacto en el rendimiento ( http://stackoverflow.com/questions/9672635/javascript-var-statement-and-performance ).
clairestreb
21
No se trata solo de la línea adicional de código, también se trata del orden de los argumentos y hacerlos opcionales. Por lo tanto, podría escribir esto con parámetros con nombre: someFunction(height: 115);pero si escribe someFunction(height);, en realidad está estableciendo el ancho.
Francisco Presencia
En ese caso, CoffeeScript admite argumentos con nombre. Te permitirá escribir solo someFunction(width = 70, height = 115);. Las variables se declaran en la parte superior del alcance actual en el código JavaScript que se genera.
Audun Olsen
6

Otra forma sería utilizar los atributos de un objeto adecuado, por ejemplo, así:

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

Por supuesto, para este ejemplo inventado es algo sin sentido. Pero en casos donde la función se ve como

do_something(some_connection_handle, some_context_parameter, some_value)

Podría ser más útil. También podría combinarse con la idea de "parameterfy" para crear dicho objeto a partir de una función existente de forma genérica. Es decir, para cada parámetro crearía un miembro que puede evaluar una versión parcial evaluada de la función.

Esta idea, por supuesto, está relacionada con Schönfinkeling, también conocido como Curry.

Udo Klein
fuente
Esta es una buena idea, y se vuelve aún mejor si la combinas con los trucos de introspección de argumentos. Desafortunadamente, no llega a trabajar con argumentos opcionales
Eric
2

Hay otra manera Si pasa un objeto por referencia, las propiedades de ese objeto aparecerán en el ámbito local de la función. Sé que esto funciona para Safari (no he comprobado otros navegadores) y no sé si esta característica tiene un nombre, pero el siguiente ejemplo ilustra su uso.

Aunque en la práctica no creo que esto ofrezca ningún valor funcional más allá de la técnica que ya está utilizando, es un poco más limpio semánticamente. Y todavía requiere pasar una referencia de objeto o un literal de objeto.

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));
Allen Ellison
fuente
2

Al probar Node-6.4.0 (process.versions.v8 = '5.0.71.60') y Node Chakracore-v7.0.0-pre8 y luego Chrome-52 (V8 = 5.2.361.49), noté que los parámetros con nombre son casi implementado, pero ese orden todavía tiene precedencia. No puedo encontrar lo que dice el estándar 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

¡Pero se requiere orden! ¿Es el comportamiento estándar?

> f(b=10)
a=10 + b=2 = 12
jgran
fuente
10
Eso no está haciendo lo que piensas. El resultado de la b=10expresión es 10, y eso es lo que se pasa a la función. f(bla=10)también funcionaría (asigna 10 a la variable blay luego pasa el valor a la función).
Matthias Kestenholz
1
Esta es también la creación ay bvariables en el ámbito global como un efecto secundario.
Gershom
2

Función fde llamada con parámetros con nombre pasados ​​como objeto

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

básicamente está llamando a su composición f(...g(o))donde estoy usando la sintaxis de propagación y ges un mapa "vinculante" que conecta los valores de los objetos con sus posiciones de parámetros.

El mapa de enlace es precisamente el ingrediente que falta, que puede representarse por la matriz de sus claves:

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

Tenga en cuenta los tres ingredientes desacoplados: la función, el objeto con argumentos con nombre y el enlace. Este desacoplamiento permite mucha flexibilidad para usar esta construcción, donde el enlace puede personalizarse arbitrariamente en la definición de la función y ampliarse arbitrariamente en el momento de la llamada a la función.

Por ejemplo, es posible que desee abreviar heighty widthcomo hy wdentro de la definición de su función, para que sea más corta y limpia, mientras que aún desea llamarlo con nombres completos para mayor claridad:

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

Esta flexibilidad también es más útil para la programación funcional, donde las funciones pueden transformarse arbitrariamente con la pérdida de sus nombres de parámetros originales.

Dmitri Zaitsev
fuente
0

Viniendo de Python esto me molestó. Escribí un contenedor / Proxy simple para el nodo que aceptará objetos posicionales y de palabras clave.

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

Vince Spicer
fuente
Si entiendo correctamente, su solución requiere distinguir los parámetros posicionales y nombrados en la definición de la función, lo que significa que no tengo la libertad de hacer esta elección en el momento de la llamada.
Dmitri Zaitsev
@DmitriZaitsev: En la comunidad de Python (con argumentos de palabras clave como una característica cotidiana), consideramos que es una muy buena idea poder obligar a los usuarios a especificar argumentos opcionales por palabra clave; Esto ayuda a evitar errores.
Tobias
@Tobias Forzar a los usuarios a hacerlo en JS es un problema resuelto: ¿ f(x, opt)dónde optestá un objeto? Si ayuda a evitar o crear errores (como los causados ​​por errores ortográficos y el dolor de recordar nombres de palabras clave) sigue siendo una pregunta.
Dmitri Zaitsev
@DmitriZaitsev: En Python esto evita estrictamente los errores, porque (por supuesto) los argumentos de palabras clave son una característica central del lenguaje allí. Para los argumentos de solo palabras clave , Python 3 tiene una sintaxis especial (mientras que en Python 2, haría estallar las claves del kwargs dict, una por una, y finalmente levantaría una TypeErrorsi las claves desconocidas se quedan). Su f(x, opt)solución permite que la ffunción haga algo como en Python 2, pero necesitaría manejar todos los valores predeterminados usted mismo.
Tobias
@Tobias ¿Es esta propuesta relevante para JS? Parece describir cómo f(x, opt)ya está funcionando, aunque no veo cómo esto ayuda a responder la pregunta, por ejemplo, dónde quiere hacer ambas cosas request(url)y request({url: url})eso no sería posible al hacer urlun parámetro de solo palabras clave.
Dmitri Zaitsev
-1

Al contrario de lo que comúnmente se cree, los parámetros con nombre pueden implementarse en JavaScript estándar de la vieja escuela (solo para parámetros booleanos) mediante una convención de codificación simple y ordenada, como se muestra a continuación.

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

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

Advertencias:

  • Se debe preservar el orden de los argumentos, pero el patrón sigue siendo útil, ya que deja en claro qué argumento real se entiende para qué parámetro formal sin tener que buscar la firma de la función o usar un IDE.

  • Esto solo funciona para booleanos. Sin embargo, estoy seguro de que se podría desarrollar un patrón similar para otros tipos utilizando la semántica de coerción de tipos única de JavaScript.

user234461
fuente
Todavía te estás refiriendo por posición, no por nombre aquí.
Dmitri Zaitsev
@DmitriZaitsev sí, incluso lo dije anteriormente. Sin embargo, el propósito de los argumentos nombrados es dejar claro al lector lo que significa cada argumento; Es una forma de documentación. Mi solución permite que la documentación se incruste dentro de la llamada de función sin recurrir a comentarios, que se ven desordenados.
user234461
Esto resuelve un problema diferente. La pregunta era sobre pasar heightpor nombre independientemente de la orden param.
Dmitri Zaitsev
@DmitriZaitsev en realidad, la pregunta no decía nada sobre el orden de los parámetros o por qué OP quería usar parámetros con nombre.
user234461
Lo hace al referirse a C # donde pasar parámetros por nombre en cualquier orden es la clave.
Dmitri Zaitsev