Funciones de flecha de ES6: Sintaxis gorda y concisa en JavaScript

En este artículo, aprenderás todo sobre la sintaxis de las funciones de flecha de JavaScript – incluyendo algunos de los inconvenientes que debes tener en cuenta al aprovechar las funciones de flecha en tu código. Verás muchos ejemplos que ilustran su funcionamiento.

Las funciones de flecha se introdujeron en JavaScript con el lanzamiento de ECMAScript 2015, también conocido como ES6. Su sintaxis concisa, y la forma en que manejan la palabra clave this, son algunas de las principales características que han contribuido al considerable éxito de las funciones de flecha entre los desarrolladores.

Convertir una función anterior a ES6 en una función de flecha

Podrías considerar las funciones como una especie de receta en la que almacenas instrucciones útiles para lograr algo que necesitas hacer en tu programa, como realizar una acción o devolver un valor. Al llamar a tu función, ejecutas los pasos incluidos en tu receta, y puedes hacerlo cada vez que llames a esa función sin necesidad de reescribir la receta una y otra vez.

Aquí tienes una forma estándar de declarar una función y luego llamarla en JavaScript:

// function declarationfunction sayHiStranger() { return 'Hi, stranger!'}// call the functionsayHiStranger()

También puedes escribir la misma función como una expresión de función, así:

const sayHiStranger = function () { return 'Hi, stranger!'}

Las funciones de las flechas son siempre expresiones. Así es como podrías reescribir la función anterior utilizando la notación de flecha gorda:

const sayHiStranger = () => 'Hi, stranger'

Los beneficios de esto incluyen:

  • sólo una línea de código
  • sin functionpalabra clave
  • sin returnpalabra clave
  • sin llaves {}

En JavaScript, las funciones son «ciudadanos de primera clase». Es decir, puedes almacenar funciones en variables, pasarlas a otras funciones como argumentos, y devolverlas desde otras funciones como valores. Puedes hacer todo esto usando funciones de flecha.

Vamos a repasar las distintas formas en las que puedes escribir funciones de flecha.

La sintaxis sin parámetros

En el ejemplo anterior, la función no tiene parámetros. En este caso, debe añadir un conjunto de paréntesis vacíos () antes del símbolo de la flecha gorda (=>). Lo mismo ocurre cuando tiene más de un parámetro:

const getNetflixSeries = (seriesName, releaseDate) => `The ${seriesName} series was released in ${releaseDate}`// call the functionconsole.log(getNetflixSeries('Bridgerton', '2020') )// output: The Bridgerton series was released in 2020

Con un solo parámetro, sin embargo, puede seguir adelante y omitir los paréntesis (no tiene que hacerlo, pero puede):

const favoriteSeries = seriesName => seriesName === "Bridgerton" ? "Let's watch it" : "Let's go out"// call the functionconsole.log(favoriteSeries("Bridgerton"))// output: "Let's watch it"

Tenga cuidado, sin embargo. Si, por ejemplo, decide utilizar un parámetro por defecto, debe envolverlo dentro de los paréntesis:

// with parentheses: correctconst bestNetflixSeries = (seriesName = "Bridgerton") => `${seriesName} is the best`// outputs: "Bridgerton is the best"console.log(bestNetflixSeries())// no parentheses: errorconst bestNetflixSeries = seriesName = "Bridgerton" => `${seriesName} is the best`// Uncaught SyntaxError: invalid arrow-function arguments (parentheses around the arrow-function may help)

Y sólo porque puedas, no significa que debas. Mezclado con un poco de sarcasmo desenfadado y bien intencionado, Kyle Simpson (de You Don’t Know JS) ha puesto sus pensamientos sobre la omisión de paréntesis en este diagrama de flujo.

Retorno implícito

Cuando sólo tienes una expresión en el cuerpo de tu función, puedes mantener todo en una línea, eliminar las llaves y prescindir de la palabra clave return. Acaba de ver cómo funcionan estas ingeniosas líneas en los ejemplos anteriores. Aquí hay un ejemplo más, por si acaso. La función orderByLikes() hace lo que dice en la lata: es decir, devuelve un array de objetos de series de Netflix ordenados por el mayor número de likes:

// using the JS sort() function to sort the titles in descending order // according to the number of likes (more likes at the top, fewer at the bottomconst orderByLikes = netflixSeries.sort( (a, b) => b.likes - a.likes )// call the function // output:the titles and the n. of likes in descending orderconsole.log(orderByLikes)

Esto es genial, pero ten cuidado con la legibilidad de tu código – especialmente cuando secuencias un montón de funciones de flecha usando líneas únicas y la sintaxis sin paréntesis, como en este ejemplo:

const greeter = greeting => name => `${greeting}, ${name}!`

¿Qué pasa ahí? Pruebe a utilizar la sintaxis de la función regular:

function greeter(greeting) { return function(name) { return `${greeting}, ${name}!` }} 

Ahora, puedes ver rápidamente cómo la función externa greeter tiene un parámetro, greeting, y devuelve una función anónima. Esta función interna, a su vez, tiene un parámetro llamado name y devuelve una cadena utilizando el valor de greeting y name. Así es como se puede llamar a la función:

const myGreet = greeter('Good morning')console.log( myGreet('Mary') ) // output: "Good morning, Mary!" 

Cuidado con estos Gotchas de Retorno Implícito

Cuando tu función de flecha contiene más de una sentencia, necesitas envolverlas todas entre llaves y usar la palabra clave return. En el código siguiente, la función construye un objeto que contiene el título y el resumen de algunas series de Netflix (las críticas de Netflix proceden del sitio web Rotten Tomatoes) :

const seriesList = netflixSeries.map( series => { const container = {} container.title = series.name container.summary = series.summary // explicit return return container} )

La función de flecha dentro de la función .map() se desarrolla sobre una serie de sentencias, al final de las cuales devuelve un objeto. Esto hace inevitable el uso de llaves alrededor del cuerpo de la función. Además, como está usando llaves, un retorno implícito no es una opción. Debe usar la palabra clave return.

Si su función de flecha devuelve un literal de objeto usando el retorno implícito, necesita envolver el objeto dentro de paréntesis redondos. Si no lo hace, se producirá un error, porque el motor JS analiza erróneamente las llaves del objeto literal como las llaves de la función. Y como acabas de notar arriba, cuando usas llaves en una función de flecha, no puedes omitir la palabra clave return.

Esta sintaxis se demuestra en la versión más corta del código anterior:

// Uncaught SyntaxError: unexpected token: ':'const seriesList = netflixSeries.map(series => { title: series.name });// Works fineconst seriesList = netflixSeries.map(series => ({ title: series.name }));

No se pueden nombrar las funciones flecha

Las funciones que no tienen un identificador de nombre entre la palabra clave function y la lista de parámetros se llaman funciones anónimas. Este es el aspecto de una expresión de función anónima regular:

const anonymous = function() { return 'You can\'t identify me!' }

Las funciones en forma de flecha son todas funciones anónimas:

const anonymousArrowFunc = () => 'You can\'t identify me!' 

A partir de ES6, las variables y los métodos pueden inferir el nombre de una función anónima a partir de su posición sintáctica, utilizando su propiedad name. Esto permite identificar la función cuando se inspecciona su valor o se informa de un error.

Comprueba esto usando anonymousArrowFunc:

console.log(anonymousArrowFunc.name)// output: "anonymousArrowFunc"

Pero tenga en cuenta que esta propiedad name inferida sólo existe cuando la función anónima se asigna a una variable, como en los ejemplos anteriores. Si utiliza una función anónima como devolución de llamada, por ejemplo, perderá esta útil característica. Esto se ejemplifica en la demostración de abajo, donde la función anónima dentro del método .setInterval() no puede hacer uso de la propiedad name:

let counter = 5let countDown = setInterval(() => { console.log(counter) counter-- if (counter === 0) { console.log("I have no name!!") clearInterval(countDown) }}, 1000)

Y eso no es todo. Esta propiedad name inferida sigue sin funcionar como un identificador propio que puedas usar para referirte a la función desde dentro de ella misma – como para la recursión, eventos de desvinculación, etc.

El anonimato intrínseco de las funciones flecha ha llevado a Kyle Simpson a expresar su opinión sobre las funciones flecha de la siguiente manera:

Como no creo que las funciones anónimas sean una buena idea para usarlas frecuentemente en tus programas, no soy un fanático de usar la forma de la función flecha =>. – You Don’t Know JS

Cómo manejan las funciones de flecha la palabra clave this

Lo más importante a recordar sobre las funciones de flecha es la forma en que manejan la palabra clave this. En particular, la palabra clave this dentro de una función de flecha no se rebota.

Para ilustrar lo que esto significa, echa un vistazo a la demostración de abajo:

Vea el Pen
JS este en funciones de flecha por SitePoint (@SitePoint)
en CodePen.

Aquí hay un botón. Al hacer clic en el botón se activa un contador inverso de 5 a 1. Los números se muestran en el propio botón:

<button class="start-btn">Start Counter</button>...const startBtn = document.querySelector(".start-btn");startBtn.addEventListener('click', function() { this.classList.add('counting') let counter = 5; const timer = setInterval(() => { this.textContent = counter counter -- if(counter < 0) { this.textContent = 'THE END!' this.classList.remove('counting') clearInterval(timer) } }, 1000) })

Nota cómo el manejador de eventos dentro del método .addEventListener() es una expresión de función anónima regular, no una función de flecha. ¿Por qué? Si registras this dentro de la función, verás que hace referencia al elemento botón al que se ha adjuntado el listener, que es exactamente lo que se espera y lo que se necesita para que el programa funcione según lo previsto:

startBtn.addEventListener('click', function() { console.log(this) ...})

Así es como se ve en la consola de las herramientas para desarrolladores de Firefox:

Sin embargo, prueba a sustituir la función normal por una función de flecha, así:

startBtn.addEventListener('click', () => { console.log(this) ...})

Ahora, this ya no hace referencia al botón. En su lugar, hace referencia al objeto Window:

Esto significa que, si quieres usar this para añadir una clase al botón después de que se pulse, por ejemplo, tu código no funcionará:

// change button's border's appearancethis.classList.add('counting')

Aquí tienes el mensaje de error en la consola:

Esto sucede porque, cuando se utiliza una función de flecha, el valor de la palabra clave this no se rebota, sino que se hereda del ámbito del padre (esto se llama ámbito léxico). En este caso concreto, la función de flecha en cuestión se está pasando como argumento al método startBtn.addEventListener(), que está en el ámbito global. En consecuencia, el this dentro del manejador de la función de flecha también está ligado al ámbito global – es decir, al objeto Window.

Por lo tanto, si desea que this haga referencia al botón de inicio en el programa, el enfoque correcto es utilizar una función regular, no una función de flecha.

La siguiente cosa a notar en la demostración anterior es el código dentro del método .setInterval(). Aquí también encontrarás una función anónima, pero esta vez es una función flecha. ¿Por qué?

Fíjate en cuál sería el valor de this si usaras una función normal:

const timer = setInterval(function() { console.log(this) ...}, 1000)

¿Sería el elemento button? En absoluto. Sería el objeto Window.

De hecho, el contexto ha cambiado, ya que this está ahora dentro de una función no ligada o global que se está pasando como argumento a .setInterval(). Por lo tanto, el valor de la palabra clave this también ha cambiado, ya que ahora está ligada al ámbito global. Un truco común en esta situación ha sido el de incluir otra variable para almacenar el valor de la palabra clave this para que siga haciendo referencia al elemento esperado – en este caso, el elemento button:

const that = thisconst timer = setInterval(function() { console.log(that) ...}, 1000)

También puede utilizar .bind() para resolver el problema:

const timer = setInterval(function() { console.log(this) ...}.bind(this), 1000)

Con las funciones de flecha, el problema desaparece por completo. Este es el valor de this cuando se utiliza una función de flecha:

const timer = setInterval( () => { console.log(this) ...}, 1000)

Esta vez, la consola registra el botón, que es exactamente lo que se necesita. De hecho, el programa va a cambiar el texto del botón, por lo que necesita this para referirse al elemento button:

const timer = setInterval( () => { console.log(this) // the button's text displays the timer value this.textContent = counter}, 1000)

Las funciones de flecha no tienen su propio contexto this. Heredan el valor de this del padre, y es por esta característica que son una gran opción en situaciones como la anterior.

Las funciones en forma de flecha no siempre son la herramienta adecuada para el trabajo

Las funciones en forma de flecha no son sólo una nueva forma elegante de escribir funciones en JavaScript. Tienen sus propias peculiaridades y limitaciones, lo que significa que hay casos en los que no conviene utilizar una función de flecha. El manejador de clics de la demostración anterior es un ejemplo, pero no es el único. Examinemos algunos más.

Funciones de flecha como métodos de objeto

Las funciones de flecha no funcionan bien como métodos en objetos. Aquí hay un ejemplo. Considere este objeto netflixSeries, que tiene algunas propiedades y un par de métodos. Llamar a console.log(netflixSeries.getLikes()) debería imprimir un mensaje con el número actual de «likes», y llamar a console.log(netflixSeries.addLike()) debería aumentar el número de «likes» en uno y luego imprimir el nuevo valor con un mensaje de agradecimiento en la consola:

const netflixSeries = { title: 'After Life', firstRealease: 2019, likes: 5, getLikes: () => `${this.title} has ${this.likes} likes`, addLike: () => { this.likes++ return `Thank you for liking ${this.title}, which now has ${this.likes} likes` } }

En cambio, llamar al método .getLikes() devuelve «undefined has NaN likes», y llamar al método .addLike() devuelve «Thank you for like undefined, which now has NaN likes». Así, this.title y this.likes fallan al referenciar las propiedades del objeto title y likes respectivamente.

Una vez más, el problema radica en el ámbito léxico de las funciones de flecha. El this dentro del método del objeto está haciendo referencia al ámbito del padre, que en este caso es el objeto Window, no el propio padre – es decir, no el objeto netflixSeries.

La solución, por supuesto, es utilizar una función regular:

const netflixSeries = { title: 'After Life', firstRealease: 2019, likes: 5, getLikes() { return `${this.title} has ${this.likes} likes` }, addLike() { this.likes++ return `Thank you for liking ${this.title}, which now has ${this.likes} likes` } }// call the methods console.log(netflixSeries.getLikes())console.log(netflixSeries.addLike())// output: After Life has 5 likesThank you for liking After Life, which now has 6 likes

Funciones de Flecha con Bibliotecas de Terceros

Otro truco a tener en cuenta es que las bibliotecas de terceros a menudo vincularán las llamadas a los métodos para que el valor this apunte a algo útil.

Por ejemplo, dentro de un manejador de eventos de jQuery, this te dará acceso al elemento del DOM al que estaba vinculado el manejador:

$('body').on('click', function() { console.log(this)})// <body>

Pero si usamos una función de flecha -que, como hemos visto, no tiene su propio contexto this– obtenemos resultados inesperados:

$('body').on('click', () =>{ console.log(this)})// Window

Aquí tienes otro ejemplo usando Vue.js:

new Vue({ el: app, data: { message: 'Hello, World!' }, created: function() { console.log(this.message); }})// Hello, World!

Dentro del hook created, this está ligado a la instancia de Vue, por lo que se muestra el mensaje «¡Hola, mundo!».

Sin embargo, si utilizamos una función de flecha, this apuntará al ámbito padre, que no tiene una propiedad message:

new Vue({ el: app, data: { message: 'Hello, World!' }, created: function() { console.log(this.message); }})// undefined

Las funciones de flecha no tienen argumentos Objeto

A veces, puede ser necesario crear una función con un número indefinido de parámetros. Por ejemplo, digamos que quieres crear una función que liste tus series favoritas de Netflix ordenadas por preferencia. Sin embargo, aún no sabes cuántas series vas a incluir. JavaScript pone a tu disposición el objeto arguments, un objeto similar a un array (pero no un array completo), que almacena los valores que se pasan a la función cuando se llama.

Intenta implementar esta funcionalidad utilizando una función de flecha:

const listYourFavNetflixSeries = () => { // we need to turn the arguments into a real array // so we can use .map() const favSeries = Array.from(arguments) return favSeries.map( (series, i) => { return `${series} is my #${i +1} favorite Netflix series` } ) console.log(arguments)}console.log(listYourFavNetflixSeries('Bridgerton', 'Ozark', 'After Life')) 

Cuando llame a la función, obtendrá el siguiente mensaje de error: Uncaught ReferenceError: arguments is not defined. Lo que esto significa es que el objeto arguments no está disponible dentro de las funciones de flecha. De hecho, reemplazar la función de flecha con una función regular hace el truco:

const listYourFavNetflixSeries = function() { const favSeries = Array.from(arguments) return favSeries.map( (series, i) => { return `${series} is my #${i +1} favorite Netflix series` } ) console.log(arguments) }console.log(listYourFavNetflixSeries('Bridgerton', 'Ozark', 'After Life'))// output: 

Así que, si necesitas el objeto arguments, no puedes usar funciones de flecha.

¿Pero qué pasa si realmente quieres usar una función de flecha para replicar la misma funcionalidad? Una cosa que puedes hacer es utilizar los parámetros de reposo de ES6 (...). Aquí es cómo usted podría reescribir su función:

const listYourFavNetflixSeries = (...seriesList) => { return seriesList.map( (series, i) => { return `${series} is my #${i +1} favorite Netflix series` } ) }

Conclusión

Al usar funciones de flecha, puedes escribir líneas concisas con retorno implícito y finalmente olvidarte de los viejos hacks para resolver la unión de la palabra clave this en JavaScript. Las funciones de flecha también funcionan muy bien con métodos de array como .map(), .sort(), .forEach(), .filter() y .reduce(). Pero recuerde, las funciones de flecha no son un reemplazo para las funciones regulares de JS, y no pueden ser usadas para todo.

Si tiene alguna pregunta sobre las funciones de flecha, o necesita ayuda para hacerlas bien, le recomiendo que se pase por los amistosos foros de SitePoint, donde hay muchos programadores conocedores dispuestos a ayudar.

Deja una respuesta

Tu dirección de correo electrónico no será publicada.