Cómo utilizar argumentos y parámetros de ES6
- Argumentos versus parámetros
- Operador de propagación (…)
- Parámetros de descanso
- Parámetros predeterminados
- Desestructurando
- Pasar argumentos
- Comprobación de tipos y parámetros faltantes o adicionales
- Argumentos obligatorios
- Objeto de argumentos
- La propiedad de longitud
- Las propiedades de la persona que llama y de la persona que llama
- Objeto de argumentos en modos estricto y no estricto
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, Faraz Kelhini explorará argumentos y parámetros en detalle y verá cómo ECMAScript 6 los ha actualizado.
ECMAScript 6 (o ECMAScript 2015) es la versión más nueva del estándar ECMAScript y ha mejorado notablemente el manejo de parámetros en JavaScript. Ahora podremos utilizar parámetros de descanso, valores predeterminados y desestructuración, entre otras novedades.
En este tutorial, exploraremos argumentos y parámetros en detalle y veremos cómo ECMAScript 6 los ha actualizado.
Argumentos versus parámetros
A menudo se hace referencia a argumentos y parámetros indistintamente. Sin embargo, 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 los que se proporcionan en la declaración de la función, y los argumentos (o parámetros reales) son los que se pasan 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 apply()
método es una herramienta conveniente para pasar una matriz como argumentos a una función. Por ejemplo, se usa comúnmente con el Math.max()
método 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: NaNMath.max.apply(Math, myArray); // 50
El Math.max()
método no admite matrices; solo acepta números. Cuando se pasa una matriz a la Math.max()
función, arroja un error. Pero cuando apply()
se utiliza el método, la matriz se envía como números individuales, por lo que el Math.max()
método puede manejarlo.
Afortunadamente, con la introducción del operador de extensión en ECMAScript 6, ya no necesitamos utilizar este apply()
método. Con el operador de extensió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 extensión se expande myArray
para crear valores individuales para la función. Si bien es posible simular el operador de extensión apply()
en ECMAScript 5, la sintaxis es confusa y carece de la flexibilidad del operador de extensión. El operador de extensión no sólo es más fácil de usar, sino que también incluye más funciones. Por ejemplo, se puede utilizar varias veces y se puede combinar con otros argumentos en una function
llamada:
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 extensión es que se puede utilizar 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 constructornew (Function.prototype.bind.apply(Date, [null].concat([2016, 5, 6]))); // Mon Jun 06 2016 00:00:00 GMT-0700 (Pacific Daylight Time)
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 | Es decir móvil |
---|---|---|---|---|
46 | 27 | 8 | – | – |
Parámetros de descanso
El parámetro rest tiene la misma sintaxis que el operador de extensió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 rest se establecerá en una matriz vacía:
function myFunction(...options) { return options;}myFunction(); // []
Un parámetro de descanso es particularmente útil al crear una función variable (una función que acepta un número variable de argumentos). Al tener la ventaja de ser matrices, los parámetros restantes pueden reemplazar fácilmente el arguments
objeto (lo cual 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 function
cuerpo de 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 bucle. 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, el parámetro string
se completa con el argumento que se pasa primero, pero el resto de los argumentos se colocan en una matriz y se asignan a la variable keys
.
Usar el parámetro rest en lugar del arguments
objeto 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 function
declaración:
function logArguments(...param1, ...param2) {}logArguments(5, 10, 15); // SyntaxError: parameter after rest parameter
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 | Es decir 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 OR
operador 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 5foo(5); // 5 10foo(); // 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, utilizamos el OR
operador lógico ( ||
). 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 un defecto. Pasar 0
o null
también activará un valor predeterminado, porque 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, nullfoo(); // 10, 10
Dentro de esta función, los tipos de argumentos pasados se verifican para asegurarse de que no estén definidos antes de asignar 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 los parámetros predeterminados. Ahora podemos poner valores predeterminados directamente en la function
declaración:
function foo(a = 10, b = 10) { console.log(a, b);}foo(5); // 5 10foo(0, null); // 0 null
Como puede ver, omitir un argumento activa el valor predeterminado, pero pasarlo 0
o null
no lo hará. Incluso podemos usar funciones para recuperar valores de parámetros predeterminados:
function getParam() { alert("getParam was called"); return 3;}function multiply(param1, param2 = getParam()) { return param1 * param2;}multiply(2, 5); // 10multiply(2); // 6 (also displays an alert dialog)
Tenga en cuenta que la getParam
función se llama solo si se omite el segundo argumento. Entonces, cuando llamamos a la multiply()
función 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 function
declaración:
function myFunction(a=10, b=a) { console.log('a = ' + a + '; b = ' + b);}myFunction(); // a=10; b=10myFunction(22); // a=22; b=22myFunction(2, 4); // a=2; b=4
Incluso puedes realizar operaciones en la function
declaración:
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]
Navegadores de escritorio:
Característica | 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:
Característica | Chrome para Android | Firefox móvil | Safari Móvil | Ópera móvil | Es decir móvil |
---|---|---|---|---|---|
Soporte básico | 49 | 15 | – | – | – |
Parámetros sin valores predeterminados después del parámetro predeterminado | 46 | 26 | – | – | – |
Desestructurando
La desestructuración es una nueva característica de ECMAScript 6 que nos permite extraer valores de matrices y objetos y asignarlos a variables usando una sintaxis similar a los literales de objetos y matrices. La sintaxis es clara y fácil de entender y resulta particularmente útil al pasar 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 function
cuerpo para ver qué parámetros espera. Con parámetros desestructurados, podemos indicar claramente los parámetros en la function
declaración:
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 sólo 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 parámetros en la function
llamada:
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 los parámetros sean obligatorios, 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 predeterminado 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 verificar manualmente los parámetros no definidos y asignar valores predeterminados dentro del function
cuerpo.
Navegadores de escritorio:
Característica | 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 predeterminada | 49 | 47 | – | 14 | – | – |
Navegadores móviles:
Característica | Chrome para Android | Firefox móvil | Safari Móvil | Ópera móvil | Es decir 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 pasado por referencia se refleja globalmente, pero la modificación de un argumento pasado por valor se refleja solo 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 sólo puede pasar por valor. Cuando pasamos un argumento a una función por valor, se crea una copia de ese valor dentro del function
alcance. Por lo tanto, cualquier cambio en el valor se refleja solo dentro del archivo function
. Considere este ejemplo:
var a = 5;function increment(a) { a = ++a; console.log(a);}increment(a); // 6console.log(a); // 5
Aquí, modificar el argumento dentro de la función no tiene ningún efecto sobre 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 sí cambia la objeto subyacente.
Considere esta función:
function foo(param){ param.bar = 'new value';}obj = { bar : 'value'}console.log(obj.bar); // valuefoo(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. Luego, esta variable se pasa a la función y su modificación afectará al objeto original.
Comprobación de tipos y parámetros faltantes o adicionales
En un lenguaje fuertemente tipado, tenemos que especificar el tipo de parámetros en la function
declaración, 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 igualesundefined
. - Más argumentos que parámetros .
Los parámetros adicionales se ignorarán, pero se pueden recuperar mediante los argumentos variables especiales tipo matriz (que se analizan a continuación).
Argumentos obligatorios
Si falta un argumento en una function
llamada, 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); // okfoo(10); // Error: missing parameter
Objeto de argumentos
El soporte para parámetros de descanso se agregó a ECMAScript 4 con la intención de reemplazar el arguments
objeto, 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 dejar de apoyar el arguments
objeto.
El arguments
objeto es un objeto similar a una matriz que está disponible en todas las funciones. Permite que los argument
valores 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, se puede acceder al primer argumento en la función mediante el nombre del parámetro param1
o el objeto de argumentos arguments[0]
, pero solo se puede acceder al segundo argumento como arguments[1]
. Además, tenga en cuenta que el arguments
objeto se puede utilizar junto con argumentos con nombre.
El arguments
objeto 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 arguments
objeto:
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 restantes 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 arguments
objeto es un objeto similar a una matriz, pero carece de métodos de matriz como slice()
y foreach()
. Para utilizar métodos de matriz en el arguments
objeto, el objeto primero debe convertirse 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 utiliza como una forma rápida de convertir el arguments
objeto en una matriz. A continuación, el sort()
método ordena los elementos de la matriz y la devuelve.
ECMAScript 6 tiene una forma aún más sencilla. Array.from()
, una nueva incorporació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 de argumentos no es técnicamente una matriz, tiene una length
propiedad que se puede usar para verificar la cantidad de argumentos pasados a una función:
function countArguments() { console.log(arguments.length);}countArguments(); // 0countArguments(10, null, "string"); // 3
Al usar la length
propiedad, tenemos un mejor control sobre la cantidad de argumentos pasados a una función. Por ejemplo, si una función requiere dos argumentos para funcionar, podríamos usar la length
propiedad 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 length
propiedad. 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 de la persona que llama y de la persona que llama
La callee
propiedad se refiere a la función que se está ejecutando actualmente y se caller
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 en desuso y al intentar acceder a ellas se produce un TypeError.
La arguments.callee
propiedad es útil en funciones recursivas (una función recursiva es una función normal 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 hacer referencia a ella es mediante 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 estricto y no estricto
En el modo no estricto de ECMAScript 5, el arguments
objeto tiene una característica inusual: mantiene sus valores sincronizados con los valores de los parámetros nombrados 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 arguments
los valores ' siempre permanecen sincronizados con los valores de los parámetros nombrados, 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, arguments
se eliminó este comportamiento confuso del objeto:
"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, el cambio arguments[0]
no afecta param
y el resultado es el esperado. La salida de esta función en ECMAScript 6 es la misma que en el modo estricto de ECMAScript 5, pero tenga en cuenta que cuando se utilizan valores predeterminados en la function
declaración, el arguments
objeto 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 ningún efecto en el arguments
objeto.
Deja un comentario