¿Hay alguna manera de proporcionar parámetros con nombre en una llamada de función en JavaScript?
C#, por ejemplo, permite usar parámetros con nombre como este:
calculateBMI(70, height: 175);
Esto me parece bastante útil. ¿Cómo puedo obtener un efecto similar en JavaScript?
He intentado hacer cosas como
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
}
pero parece incómodo. ¿Hay alguna forma más simple?
ES2015 y posteriores
En ES2015, la desestructuración de parámetros se puede utilizar para simular parámetros con nombre. Requeriría que la persona que llama pase un objeto, pero puede evitar todas las comprobaciones dentro de la función si también usa los 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 de la implementación hasta cierto punto, por lo que puede no ser compatible con todos los 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 función podría verse así
func(a, b, {someArg: ..., someOtherArg: ...});
donde a
y b
son argumentos posicionales y el último argumento es un objeto con argumentos con nombre.
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
Este enfoque tiene algunos inconvenientes (¡estás advertido!):
- Si el último argumento es un objeto, se trata como un "objeto de argumento con nombre".
- Siempre obtendrás tantos argumentos como definiste en la función, pero algunos de ellos pueden tener el valor
undefined
(eso es diferente a no tener ningún valor). Eso significa que no puede utilizarloarguments.length
para 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 ampliar Function.prototype
para que puedas hacer:
foo.execute(a, b, {posArg: ...});
No, el enfoque de objetos 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.
Mucha gente dice que se debe utilizar simplemente el truco "Pasar un objeto" para tener parámetros con nombre.
/**
* 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 que existen, muchos de nosotros, los desarrolladores, confiamos en sugerencias de tipo/argumento dentro de nuestro IDE. Yo personalmente uso PhpStorm (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 llamas a la función, el IDE te da una pista de tipo único y eso es todo... ¿Cómo se supone que vamos a saber qué parámetros y tipos deben ir en el objeto 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 mantienes 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 realizar una tarea... Entonces, si hay un problema con esta técnica, 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 código nuevo es fácil de escribir ya que mi IDE me brinda todas las sugerencias de argumentos adecuados. Y, mientras mantengo el código más adelante, puedo ver de un vistazo no solo el valor pasado a la función, sino también el nombre del argumento. La única sobrecarga que veo es declarar los nombres de los argumentos como variables locales para evitar contaminar el espacio de nombres global. Claro, es un poco más de escritura, pero es trivial en comparación con el tiempo que lleva buscar bloques de documentos mientras se escribe código nuevo o se mantiene el código existente.
Actualización - 2022
JavaScript ahora tiene la capacidad de tener algo parecido a parámetros con nombre mediante la desestructuración de objetos disponible en ES6. La mayoría de los navegadores más nuevos pueden usar esta función Ver soporte del navegador
Así es como funciona:
// Define your function like this
function myFunc({arg1, arg2, arg3}) {
// Function body
}
// Call your function like this
myFunc({arg1: "value1", arg2: "value2", arg3: "value3"})
// You can also have default values for arguments
function myFunc2({firstName, lastName, age = 21}) {
// Function body
}
// And you can call it with or without an "age" argument
myFunc({firstName: "John", lastName: "Doe"}) // Age will be 21
myFunc({firstName: "Jane", lastName: "Doe", age: 22})
La mejor parte es que la mayoría de los IDE ahora admiten esta sintaxis y obtienes un buen soporte de sugerencias de argumentos.
Mecanografiado
Para aquellos de ustedes que usan TypeScript, pueden hacer lo mismo usando esta sintaxis.
function myFunc(
{firstName, lastName, age = 21}:
{firstName: string, lastName: string, age?: number}
) {
// Function body
}
O, usando una interfaz
interface Params {
firstName: string
lastName: string
age?: number
}
function myFunc({firstName, lastName, age = 21}: Params) {
// Function body
}