Cómo usar los argumentos y parámetros de ES6
Publicado: 2022-03-10ECMAScript 6 (o ECMAScript 2015) es la versión más reciente del estándar ECMAScript y ha mejorado notablemente el manejo de parámetros en JavaScript. Ahora podemos usar parámetros de descanso, valores predeterminados y desestructuración, entre otras características nuevas.
En este tutorial, exploraremos argumentos y parámetros en detalle y veremos cómo ECMAScript 6 los ha actualizado.
Argumentos versus parámetros
Los argumentos y los parámetros a menudo se denominan indistintamente. No obstante, a los efectos de este tutorial, haremos una distinción. En la mayoría de los estándares, los parámetros (o parámetros formales) son lo que se da en la declaración de la función, y los argumentos (o parámetros reales) son lo que se pasa a la función. Considere esta función:
function foo(param1, param2) { // do something } foo(10, 20);
En esta función, param1
y param2
son parámetros de función y los valores pasados a la función ( 10
y 20
) son argumentos.
Operador de propagación (…)
En ECMAScript 5, el método apply()
es una herramienta conveniente para pasar una matriz como argumentos a una función. Por ejemplo, se usa comúnmente con el método Math.max()
para encontrar el valor más alto en una matriz. Considere este fragmento de código:
var myArray = [5, 10, 50]; Math.max(myArray); // Error: NaN Math.max.apply(Math, myArray); // 50
El método Math.max()
no admite matrices; solo acepta números. Cuando se pasa una matriz a la función Math.max()
, arroja un error. Pero cuando se usa el método apply()
, la matriz se envía como números individuales, por lo que el método Math.max()
puede manejarlo.
Afortunadamente, con la introducción del operador de propagación en ECMAScript 6, ya no necesitamos usar el método apply()
. Con el operador de propagación, podemos expandir fácilmente una expresión en múltiples argumentos:
var myArray = [5, 10, 50]; Math.max(...myArray); // 50
Aquí, el operador de dispersión expande myArray
para crear valores individuales para la función. Si bien es posible simular el operador de distribución mediante apply()
en ECMAScript 5, la sintaxis es confusa y carece de la flexibilidad del operador de distribución. El operador de propagación no solo es más fácil de usar, sino que incluye más funciones. Por ejemplo, se puede usar varias veces y se puede mezclar con otros argumentos en una llamada de function
:
function myFunction() { for(var i in arguments){ console.log(arguments[i]); } } var params = [10, 15]; myFunction(5, ...params, 20, ...[25]); // 5 10 15 20 25
Otra ventaja del operador de propagación es que se puede usar fácilmente con constructores:
new Date(...[2016, 5, 6]); // Mon Jun 06 2016 00:00:00 GMT-0700 (Pacific Daylight Time)
Por supuesto, podríamos reescribir el código anterior en ECMAScript 5, pero necesitaríamos usar un patrón complicado para evitar obtener un error de tipo:
new Date.apply(null, [2016, 4, 24]); // TypeError: Date.apply is not a constructor new (Function.prototype.bind.apply(Date, [null].concat([2016, 5, 6]))); // Mon Jun 06 2016 00:00:00 GMT-0700 (Pacific Daylight Time)
Compatibilidad con navegadores de operadores extendidos en llamadas de funciones
Navegadores de escritorio:
Cromo | Firefox | explorador de Internet | Borde de Microsoft | Ópera | Safari |
---|---|---|---|---|---|
46 | 27 | – | Soportado | – | 7.1 |
Navegadores móviles:
Chrome para Android | Firefox móvil | safari móvil | Ópera Móvil | IE móvil |
---|---|---|---|---|
46 | 27 | 8 | – | – |
Parámetros de descanso
El parámetro rest tiene la misma sintaxis que el operador de propagación, pero en lugar de expandir una matriz en parámetros, recopila parámetros y los convierte en una matriz.
function myFunction(...options) { return options; } myFunction('a', 'b', 'c'); // ["a", "b", "c"]
Si no hay argumentos, el parámetro resto se establecerá en una matriz vacía:
function myFunction(...options) { return options; } myFunction(); // []
Un parámetro de descanso es particularmente útil cuando se crea una función variádica (una función que acepta un número variable de argumentos). Al tener la ventaja de ser matrices, los parámetros de descanso pueden reemplazar fácilmente el objeto de arguments
(que explicaremos más adelante en este tutorial). Considere esta función, escrita en ECMAScript 5:
function checkSubstrings(string) { for (var i = 1; i < arguments.length; i++) { if (string.indexOf(arguments[i]) === -1) { return false; } } return true; } checkSubstrings('this is a string', 'is', 'this'); // true
Esta función comprueba si una cadena contiene varias subcadenas. El primer problema con esta función es que tenemos que mirar dentro del cuerpo de la function
para ver que toma múltiples argumentos. El segundo problema es que la iteración debe comenzar desde 1
en lugar de 0
, porque arguments[0]
apunta al primer argumento. Si luego decidimos agregar otro parámetro antes o después de la cadena, es posible que nos olvidemos de actualizar el ciclo. Con el resto de parámetros, evitamos fácilmente estos problemas:
function checkSubstrings(string, ...keys) { for (var key of keys) { if (string.indexOf(key) === -1) { return false; } } return true; } checkSubstrings('this is a string', 'is', 'this'); // true
La salida de esta función es la misma que la anterior. Aquí nuevamente, la string
parámetros se llena con el argumento que se pasa primero, pero el resto de los argumentos se colocan en una matriz y se asignan a las keys
variables.
Usar el parámetro rest en lugar del objeto arguments
mejora la legibilidad del código y evita problemas de optimización en JavaScript. Sin embargo, el parámetro de descanso no está exento de limitaciones. Por ejemplo, debe ser el último argumento; de lo contrario, se producirá un error de sintaxis:
function logArguments(a, ...params, b) { console.log(a, params, b); } logArguments(5, 10, 15); // SyntaxError: parameter after rest parameter
Otra limitación es que solo se permite un parámetro de descanso en la declaración de la function
:
function logArguments(...param1, ...param2) { } logArguments(5, 10, 15); // SyntaxError: parameter after rest parameter
Compatibilidad con el navegador de parámetros de reposo
Navegadores de escritorio:
Cromo | Firefox | explorador de Internet | Borde de Microsoft | Ópera | Safari |
---|---|---|---|---|---|
47 | 15 | – | Soportado | 34 | – |
Navegadores móviles:
Chrome para Android | Firefox móvil | safari móvil | Ópera Móvil | IE móvil |
---|---|---|---|---|
47 | 15 | – | – | – |
Parámetros predeterminados
Parámetros predeterminados en ECMAScript 5
JavaScript no admite parámetros predeterminados en ECMAScript 5, pero existe una solución sencilla. Usando un operador OR
lógico ( ||
) dentro de la función, podemos simular fácilmente los parámetros predeterminados en ECMAScript 5. Considere esta función:
function foo(param1, param2) { param1 = param1 || 10; param2 = param2 || 10; console.log(param1, param2); } foo(5, 5); // 5 5 foo(5); // 5 10 foo(); // 10 10
Esta función espera dos argumentos, pero cuando se llama sin argumentos, utilizará los valores predeterminados. Dentro de la función, los argumentos que faltan se establecen automáticamente como indefinidos; entonces, podemos detectar estos argumentos y declarar valores predeterminados para ellos. Para detectar argumentos faltantes y establecer valores predeterminados, usamos el operador lógico OR
( ||
). Este operador examina su primer argumento: si es verdadero, el operador lo devuelve; si no es así, el operador devuelve su segundo argumento.
Este enfoque se usa comúnmente en funciones, pero tiene una falla. Pasar 0
o null
también activará un valor predeterminado, porque estos se consideran valores falsos. Entonces, si realmente necesitamos pasar 0
o null
a esta función, necesitaríamos una forma alternativa de verificar si falta un argumento:
function foo(param1, param2) { if(param1 === undefined){ param1 = 10; } if(param2 === undefined){ param2 = 10; } console.log(param1, param2); } foo(0, null); // 0, null foo(); // 10, 10
Dentro de esta función, los tipos de argumentos pasados se verifican para asegurarse de que no estén definidos antes de que se asignen los valores predeterminados. Este enfoque requiere solo un poco más de código, pero es una alternativa más segura y nos permite pasar 0
y null
a la función.
Parámetros predeterminados en ECMAScript 6
Con ECMAScript 6, ya no necesitamos buscar valores indefinidos para simular parámetros predeterminados. Ahora podemos poner los valores predeterminados directamente en la declaración de la function
:
function foo(a = 10, b = 10) { console.log(a, b); } foo(5); // 5 10 foo(0, null); // 0 null
Como puede ver, omitir un argumento activa el valor predeterminado, pero pasar 0
o null
no lo hará. Incluso podemos usar funciones para recuperar valores para parámetros predeterminados:
function getParam() { alert("getParam was called"); return 3; } function multiply(param1, param2 = getParam()) { return param1 * param2; } multiply(2, 5); // 10 multiply(2); // 6 (also displays an alert dialog)
Tenga en cuenta que la función getParam
solo se llama si se omite el segundo argumento. Entonces, cuando llamamos a la función multiply()
con dos parámetros, la alerta no se mostrará.
Otra característica interesante de los parámetros predeterminados es que podemos hacer referencia a otros parámetros y variables en la declaración de la function
:
function myFunction(a=10, b=a) { console.log('a = ' + a + '; b = ' + b); } myFunction(); // a=10; b=10 myFunction(22); // a=22; b=22 myFunction(2, 4); // a=2; b=4
Incluso puede realizar operaciones en la declaración de la function
:
function myFunction(a, b = ++a, c = a*b) { console.log(c); } myFunction(5); // 36
Tenga en cuenta que, a diferencia de otros lenguajes, JavaScript evalúa los parámetros predeterminados en el momento de la llamada:
function add(value, array = []) { array.push(value); return array; } add(5); // [5] add(6); // [6], not [5, 6]
Compatibilidad con el explorador de parámetros predeterminado
Navegadores de escritorio:
Rasgo | Cromo | Firefox | explorador de Internet | Borde de Microsoft | Ópera | Safari |
---|---|---|---|---|---|---|
Soporte básico | 49 | 15 | – | 14 | – | – |
Parámetros sin valores predeterminados después del parámetro predeterminado | 49 | 26 | – | 14 | – | – |
Navegadores móviles:
Rasgo | Chrome para Android | Firefox móvil | safari móvil | Ópera Móvil | IE móvil |
---|---|---|---|---|---|
Soporte básico | 49 | 15 | – | – | – |
Parámetros sin valores predeterminados después del parámetro predeterminado | 46 | 26 | – | – | – |
desestructuración
La desestructuración es una característica nueva en ECMAScript 6 que nos permite extraer valores de matrices y objetos y asignarlos a variables usando una sintaxis que es similar a los literales de objetos y matrices. La sintaxis es clara y fácil de entender y es particularmente útil cuando se pasan argumentos a una función.
En ECMAScript 5, un objeto de configuración se usa a menudo para manejar una gran cantidad de parámetros opcionales, especialmente cuando el orden de las propiedades no importa. Considere esta función:
function initiateTransfer(options) { var protocol = options.protocol, port = options.port, delay = options.delay, retries = options.retries, timeout = options.timeout, log = options.log; // code to initiate transfer } options = { protocol: 'http', port: 800, delay: 150, retries: 10, timeout: 500, log: true }; initiateTransfer(options);
Los desarrolladores de JavaScript suelen utilizar este patrón y funciona bien, pero tenemos que mirar dentro del cuerpo de la function
para ver qué parámetros espera. Con parámetros desestructurados, podemos indicar claramente los parámetros en la declaración de la function
:
function initiateTransfer({protocol, port, delay, retries, timeout, log}) { // code to initiate transfer }; var options = { protocol: 'http', port: 800, delay: 150, retries: 10, timeout: 500, log: true } initiateTransfer(options);
En esta función, hemos utilizado un patrón de desestructuración de objetos, en lugar de un objeto de configuración. Esto hace que nuestra función no solo sea más concisa, sino también más fácil de leer.
También podemos combinar parámetros desestructurados con regulares:
function initiateTransfer(param1, {protocol, port, delay, retries, timeout, log}) { // code to initiate transfer } initiateTransfer('some value', options);
Tenga en cuenta que se generará un error de tipo si se omiten los parámetros en la llamada a la function
:
function initiateTransfer({protocol, port, delay, retries, timeout, log}) { // code to initiate transfer } initiateTransfer(); // TypeError: Cannot match against 'undefined' or 'null'
Este es el comportamiento deseado cuando necesitamos que se requieran parámetros, pero ¿qué pasa si queremos que sean opcionales? Para evitar este error cuando faltan parámetros, debemos asignar un valor predeterminado a los parámetros desestructurados:
function initiateTransfer({protocol, port, delay, retries, timeout, log} = {}) { // code to initiate transfer } initiateTransfer(); // no error
En esta función, se proporciona un objeto vacío como valor predeterminado para los parámetros desestructurados. Ahora, si se llama a esta función sin ningún parámetro, no se producirá ningún error.
También podemos asignar un valor por defecto a cada parámetro desestructurado:
function initiateTransfer({ protocol = 'http', port = 800, delay = 150, retries = 10, timeout = 500, log = true }) { // code to initiate transfer }
En este ejemplo, cada propiedad tiene un parámetro predeterminado, lo que elimina la necesidad de que verifiquemos manualmente los parámetros no definidos y asignemos valores predeterminados dentro del cuerpo de la function
.
Desestructuración del soporte del navegador
Navegadores de escritorio:
Rasgo | Cromo | Firefox | explorador de Internet | Borde de Microsoft | Ópera | Safari |
---|---|---|---|---|---|---|
Soporte básico | 49 | 2.0 | – | 14 | – | 7.1 |
Parámetro desestructurado con asignación de valor por defecto | 49 | 47 | – | 14 | – | – |
Navegadores móviles:
Rasgo | Chrome para Android | Firefox móvil | safari móvil | Ópera Móvil | IE móvil |
---|---|---|---|---|---|
Soporte básico | 49 | 1 | 8 | – | – |
Parámetros sin valores predeterminados después del parámetro predeterminado | 49 | 47 | – | – | – |
pasar argumentos
Hay dos formas de pasar argumentos a una función: por referencia o por valor. La modificación de un argumento que se pasa por referencia se refleja globalmente, pero la modificación de un argumento que se pasa por valor solo se refleja dentro de la función.
En algunos lenguajes, como Visual Basic y PowerShell, tenemos la opción de especificar si pasar un argumento por referencia o por valor, pero ese no es el caso con JavaScript.
Pasar argumentos por valor
Técnicamente, JavaScript solo puede pasar por valor. Cuando pasamos un argumento a una función por valor, se crea una copia de ese valor dentro del alcance de la function
. Por lo tanto, cualquier cambio en el valor se refleja solo dentro de la function
. Considere este ejemplo:
var a = 5; function increment(a) { a = ++a; console.log(a); } increment(a); // 6 console.log(a); // 5
Aquí, modificar el argumento dentro de la función no tiene efecto en el valor original. Entonces, cuando la variable se registra desde fuera de la función, el valor impreso sigue siendo 5
.
Pasar argumentos por referencia
En JavaScript, todo se pasa por valor, pero cuando pasamos una variable que hace referencia a un objeto (incluidas las matrices), el "valor" es una referencia al objeto, y cambiar una propiedad de un objeto al que hace referencia una variable cambia el objeto subyacente.
Considere esta función:
function foo(param){ param.bar = 'new value'; } obj = { bar : 'value' } console.log(obj.bar); // value foo(obj); console.log(obj.bar); // new value
Como puede ver, la propiedad del objeto se modifica dentro de la función, pero el valor modificado es visible fuera de la función.
Cuando pasamos un valor no primitivo, como una matriz u objeto, detrás de escena se crea una variable que apunta a la ubicación del objeto original en la memoria. Esta variable luego se pasa a la función, y modificarla afectará el objeto original.
Comprobación de tipos y parámetros extra o faltantes
En un lenguaje fuertemente tipado, tenemos que especificar el tipo de parámetros en la declaración de la function
, pero JavaScript carece de esta característica. En JavaScript, no importa qué tipo de datos o cuántos argumentos pasemos a una función.
Supongamos que tenemos una función que acepta solo un argumento. Cuando llamamos a esa función, no estamos limitados a pasar solo un argumento a la función; ¡somos libres de pasar uno, dos o más argumentos! Incluso podemos optar por no pasar nada en absoluto, y no se producirán errores.
El número de argumentos y parámetros puede diferir de dos maneras:
- Menos argumentos que parámetros .
Los parámetros que faltan serán iguales aundefined
. - Más argumentos que parámetros .
Los parámetros adicionales se ignorarán, pero se pueden recuperar a través de los argumentos variables especiales tipo matriz (que se analizan a continuación).
Argumentos Obligatorios
Si falta un argumento en una llamada de function
, se establecerá en undefined
. Podemos aprovechar este comportamiento y arrojar un error si se omite un argumento:
function foo(mandatory, optional) { if (mandatory === undefined) { throw new Error('Missing parameter: mandatory'); } }
En ECMAScript 6, podemos llevar esto más allá y usar parámetros predeterminados para establecer argumentos obligatorios:
function throwError() { throw new Error('Missing parameter'); } function foo(param1 = throwError(), param2 = throwError()) { // do something } foo(10, 20); // ok foo(10); // Error: missing parameter
Objeto Argumentos
El soporte para parámetros de descanso se agregó a ECMAScript 4 con la intención de reemplazar el objeto de arguments
, pero ECMAScript 4 nunca llegó a buen término. Con el lanzamiento de ECMAScript 6, JavaScript ahora admite oficialmente el resto de parámetros. También rechazó el plan de eliminar el soporte para el objeto de arguments
.
El objeto arguments
es un objeto similar a una matriz que está disponible en todas las funciones. Permite que los valores del argument
pasados a la función se recuperen por número, en lugar de por nombre. El objeto nos permite pasar cualquier número de argumentos a una función. Considere el siguiente fragmento de código:
function checkParams(param1) { console.log(param1); // 2 console.log(arguments[0], arguments[1]); // 2 3 console.log(param1 + arguments[0]); // 2 + 2 } checkParams(2, 3);
Esta función espera recibir solo un argumento. Cuando lo llamamos con dos argumentos, el primer argumento es accesible en la función por el nombre de parámetro param1
o el objeto arguments[0]
, pero el segundo argumento solo es accesible como arguments[1]
. Además, tenga en cuenta que el objeto arguments
se puede usar junto con argumentos con nombre.
El objeto arguments
contiene una entrada para cada argumento pasado a la función y el índice de la primera entrada comienza en 0
. Si quisiéramos acceder a más argumentos en el ejemplo anterior, escribiríamos arguments[2]
, arguments[3]
y así sucesivamente.
Incluso podríamos omitir la configuración de parámetros con nombre por completo y simplemente usar el objeto de arguments
:
function checkParams() { console.log(arguments[1], arguments[0], arguments[2]); } checkParams(2, 4, 6); // 4 2 6
De hecho, los parámetros con nombre son una conveniencia, no una necesidad. De manera similar, los parámetros de descanso se pueden usar para reflejar los argumentos pasados:
function checkParams(...params) { console.log(params[1], params[0], params[2]); // 4 2 6 console.log(arguments[1], arguments[0], arguments[2]); // 4 2 6 } checkParams(2, 4, 6);
El objeto arguments
es un objeto similar a una matriz, pero carece de métodos de matriz como slice()
y foreach()
. Para usar métodos de matriz en el objeto de arguments
, primero se debe convertir el objeto en una matriz real:
function sort() { var a = Array.prototype.slice.call(arguments); return a.sort(); } sort(40, 20, 50, 30); // [20, 30, 40, 50]
En esta función, Array.prototype.slice.call()
se usa como una forma rápida de convertir el objeto de arguments
en una matriz. A continuación, el método sort()
ordena los elementos de la matriz y los devuelve.
ECMAScript 6 tiene una forma aún más sencilla. Array.from()
, una nueva adición en ECMAScript 6, crea una nueva matriz a partir de cualquier objeto similar a una matriz:
function sort() { var a = Array.from(arguments); return a.sort(); } sort(40, 20, 50, 30); // [20, 30, 40, 50]
La propiedad de longitud
Aunque el objeto arguments no es técnicamente una matriz, tiene una propiedad de length
que se puede usar para verificar la cantidad de argumentos que se pasan a una función:
function countArguments() { console.log(arguments.length); } countArguments(); // 0 countArguments(10, null, "string"); // 3
Al usar la propiedad de length
, tenemos un mejor control sobre la cantidad de argumentos que se pasan a una función. Por ejemplo, si una función requiere dos argumentos para funcionar, podríamos usar la propiedad de length
para verificar la cantidad de argumentos pasados y arrojar un error si son menos de lo esperado:
function foo(param1, param2) { if (arguments.length < 2) { throw new Error("This function expects at least two arguments"); } else if (arguments.length === 2) { // do something } }
Los parámetros de descanso son matrices, por lo que tienen una propiedad de length
. En ECMAScript 6, el código anterior se puede reescribir con parámetros de descanso:
function foo(...params) { if (params.length < 2) { throw new Error("This function expects at least two arguments"); } else if (params.length === 2) { // do something } }
Las propiedades del destinatario y del llamante
La callee
de la persona que llama se refiere a la función que se está ejecutando actualmente, y la persona que caller
se refiere a la función que ha llamado a la función que se está ejecutando actualmente. En el modo estricto de ECMAScript 5, estas propiedades están obsoletas y al intentar acceder a ellas se produce un TypeError.
La propiedad arguments.callee
es útil en funciones recursivas (una función recursiva es una función regular que se refiere a sí misma por su nombre), especialmente cuando el nombre de la función no está disponible (una función anónima). Debido a que una función anónima no tiene nombre, la única forma de referirse a ella es arguments.callee
.
var result = (function(n) { if (n <= 1) { return 1; } else { return n * arguments.callee(n - 1); } })(4); // 24
Objeto de argumentos en modos estrictos y no estrictos
En el modo no estricto de ECMAScript 5, el objeto arguments
tiene una característica inusual: mantiene sus valores sincronizados con los valores de los parámetros con nombre correspondientes.
Considere el siguiente fragmento de código:
function foo(param) { console.log(param === arguments[0]); // true arguments[0] = 500; console.log(param === arguments[0]); // true return param } foo(200); // 500
Dentro de esta función, se asigna un nuevo valor a arguments[0]
. Debido a que los valores de los arguments
siempre están sincronizados con los valores de los parámetros con nombre, el cambio a arguments[0]
también cambiará el valor de param
. De hecho, son como dos nombres diferentes para la misma variable. En el modo estricto de ECMAScript 5, se eliminó este comportamiento confuso del objeto arguments
:
"use strict"; function foo(param) { console.log(param === arguments[0]); // true arguments[0] = 500; console.log(param === arguments[0]); // false return param } foo(200); // 200
Esta vez, cambiar arguments[0]
no afecta a param
y el resultado es el esperado. El resultado de esta función en ECMAScript 6 es el mismo que en el modo estricto de ECMAScript 5, pero tenga en cuenta que cuando se utilizan valores predeterminados en la declaración de la function
, el objeto de arguments
no se ve afectado:
function foo(param1, param2 = 10, param3 = 20) { console.log(param1 === arguments[0]); // true console.log(param2 === arguments[1]); // true console.log(param3 === arguments[2]); // false console.log(arguments[2]); // undefined console.log(param3); // 20 } foo('string1', 'string2');
En esta función, aunque param3
tiene un valor predeterminado, no es igual a arguments[2]
porque solo se pasan dos argumentos a la función. En otras palabras, establecer valores predeterminados no tiene efecto en el objeto de arguments
.
Conclusión
ECMAScript 6 ha traído cientos de pequeñas y grandes mejoras a JavaScript. Cada vez más, los desarrolladores utilizan las funciones de ECMAScript 6 y pronto estas funciones serán inevitables. En este tutorial, hemos aprendido cómo ECMAScript 6 ha mejorado el manejo de parámetros en JavaScript, pero solo hemos arañado la superficie de ECMAScript 6. Vale la pena revisar muchas otras características nuevas e interesantes del lenguaje.
Enlaces
- Tabla de compatibilidad de ECMAScript 6, Juriy Zaytsev
- "ECMAScript 2015 Language Specification", ECMA International