Funzioni freccia ES6: Sintassi grassa e concisa in JavaScript

In questo articolo, imparerai tutto sulla sintassi delle funzioni freccia di JavaScript – compresi alcuni degli intoppi di cui devi essere consapevole quando usi le funzioni freccia nel tuo codice. Vedrete molti esempi che illustrano come funzionano.

Le funzioni freccia sono state introdotte in JavaScript con il rilascio di ECMAScript 2015, noto anche come ES6. La loro sintassi concisa, e il modo in cui gestiscono la parola chiave this, sono tra le caratteristiche principali che hanno contribuito al notevole successo delle funzioni freccia tra gli sviluppatori.

Trasformare una funzione pre-ES6 in una funzione freccia

Potreste considerare le funzioni come una sorta di ricetta in cui memorizzate istruzioni utili per realizzare qualcosa che avete bisogno di fare nel vostro programma, come eseguire un’azione o restituire un valore. Chiamando la vostra funzione, eseguite i passi inclusi nella vostra ricetta, e potete farlo ogni volta che chiamate quella funzione senza bisogno di riscrivere la ricetta ancora e ancora.

Ecco un modo standard per dichiarare una funzione e poi chiamarla in JavaScript:

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

Puoi anche scrivere la stessa funzione come espressione di funzione, come questa:

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

Le funzioni freccia sono sempre espressioni. Ecco come potreste riscrivere la funzione di cui sopra usando la notazione di freccia grassa:

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

I vantaggi di questo includono:

  • solo una linea di codice
  • no function parola chiave
  • no return parola chiave
  • no parentesi graffe {}

In JavaScript, le funzioni sono “cittadini di prima classe”. Cioè, potete memorizzare le funzioni in variabili, passarle ad altre funzioni come argomenti, e restituirle da altre funzioni come valori. Potete fare tutto questo usando le funzioni freccia.

Passiamo attraverso i vari modi in cui potete scrivere funzioni freccia.

La sintassi No Parens

Nell’esempio precedente, la funzione non ha parametri. In questo caso, dovete aggiungere una serie di parentesi vuote () prima del simbolo della freccia grassa (=>). Lo stesso vale quando avete più di un parametro:

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 parametro, tuttavia, potete andare avanti e lasciare fuori le parentesi (non è necessario, ma potete):

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

Fate attenzione, però. Se, per esempio, decidete di usare un parametro predefinito, dovete avvolgerlo tra parentesi:

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

E solo perché puoi, non significa che dovresti. Mescolato con un po’ di sarcasmo spensierato e ben intenzionato, Kyle Simpson (della fama di You Don’t Know JS) ha messo i suoi pensieri sull’omissione delle parentesi in questo diagramma di flusso.

Implicit Return

Quando avete solo un’espressione nel corpo della vostra funzione, potete tenere tutto su una linea, rimuovere le parentesi graffe, e fare a meno della parola chiave return. Avete appena visto come funzionano questi ingegnosi one-liner negli esempi precedenti. Qui c’è un altro esempio, giusto per sicurezza. La funzione orderByLikes() fa quello che dice sulla scatola: cioè, restituisce un array di oggetti serie Netflix ordinati per il più alto numero di “mi piace”:

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

Questo è bello, ma tenete d’occhio la leggibilità del vostro codice – specialmente quando mettete in sequenza una serie di funzioni freccia usando le one-liner e la sintassi senza parentesi, come in questo esempio:

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

Cosa sta succedendo qui? Provate a usare la sintassi regolare della funzione:

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

Ora, potete vedere rapidamente come la funzione esterna greeter abbia un parametro, greeting, e restituisca una funzione anonima. Questa funzione interna ha a sua volta un parametro chiamato name e restituisce una stringa usando il valore di entrambi greeting e name. Ecco come potete chiamare la funzione:

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

Attenzione a questi errori di ritorno implicito

Quando la vostra funzione freccia contiene più di una dichiarazione, dovete avvolgerle tutte in parentesi graffe e usare la parola chiave return. Nel codice qui sotto, la funzione costruisce un oggetto contenente il titolo e il riassunto di alcune serie di Netflix (le recensioni di Netflix sono dal sito Rotten Tomatoes) :

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

La funzione freccia all’interno della funzione .map() si sviluppa su una serie di dichiarazioni, alla fine delle quali restituisce un oggetto. Questo rende inevitabile l’uso delle parentesi graffe intorno al corpo della funzione. Inoltre, poiché state usando le parentesi graffe, un ritorno implicito non è un’opzione. Dovete usare la parola chiave return.

Se la vostra funzione freccia restituisce un letterale di oggetto usando il ritorno implicito, dovete avvolgere l’oggetto dentro parentesi tonde. Non farlo comporterà un errore, perché il motore JS analizza erroneamente le parentesi graffe del letterale dell’oggetto come le parentesi graffe della funzione. E come avete appena notato sopra, quando usate le parentesi graffe in una funzione freccia, non potete omettere la parola chiave return.

Questa sintassi è dimostrata nella versione più breve del codice precedente:

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

You Can’t Name Arrow Functions

Le funzioni che non hanno un identificatore di nome tra la parola chiave function e la lista dei parametri sono chiamate funzioni anonime. Ecco come appare un’espressione regolare di funzione anonima:

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

Le funzioni freccia sono tutte funzioni anonime:

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

A partire da ES6, le variabili e i metodi possono dedurre il nome di una funzione anonima dalla sua posizione sintattica, utilizzando la proprietà name. Questo rende possibile identificare la funzione quando si ispeziona il suo valore o si segnala un errore.

Controlla questo utilizzando anonymousArrowFunc:

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

Ma sappiate che questa proprietà name dedotta esiste solo quando la funzione anonima è assegnata a una variabile, come negli esempi precedenti. Se usate una funzione anonima come callback, per esempio, perdete questa utile caratteristica. Questo è esemplificato nel demo qui sotto dove la funzione anonima all’interno del metodo .setInterval() non può avvalersi della proprietà name:

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

E non è tutto. Questa proprietà name dedotta non funziona ancora come un vero e proprio identificatore che si può usare per riferirsi alla funzione dall’interno della stessa – come per la ricorsione, gli eventi di unbinding, ecc.

L’anonimato intrinseco delle funzioni freccia ha portato Kyle Simpson ad esprimere la sua opinione sulle funzioni freccia come segue:

Siccome non credo che le funzioni anonime siano una buona idea da usare frequentemente nei programmi, non sono un fan dell’uso della forma di funzione freccia =>. – Tu non conosci JS

Come le funzioni freccia gestiscono la parola chiave this

La cosa più importante da ricordare sulle funzioni freccia è il modo in cui gestiscono la parola chiave this. In particolare, la parola chiave this all’interno di una funzione freccia non viene rimbalzata.

Per illustrare cosa significa, guardate il demo qui sotto:

Vedi la penna
JS questo nelle funzioni freccia da SitePoint (@SitePoint)
su CodePen.

Ecco un pulsante. Facendo clic sul pulsante si attiva un contatore inverso da 5 a 1. I numeri vengono visualizzati sul pulsante stesso:

<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 come il gestore dell’evento all’interno del metodo .addEventListener() sia una regolare espressione di funzione anonima, non una funzione freccia. Perché? Se registrate this all’interno della funzione, vedrete che fa riferimento all’elemento pulsante a cui è stato collegato l’ascoltatore, che è esattamente ciò che ci si aspetta e che è necessario affinché il programma funzioni come previsto:

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

Ecco come appare nella console degli strumenti di sviluppo di Firefox:

Tuttavia, prova a sostituire la funzione regolare con una funzione freccia, come questa:

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

Ora, this non fa più riferimento al pulsante. Invece, fa riferimento all’oggetto Window:

Questo significa che, se vuoi usare this per aggiungere una classe al pulsante dopo che è stato cliccato, per esempio, il tuo codice non funzionerà:

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

Ecco il messaggio di errore nella console:

Questo succede perché, quando usate una funzione freccia, il valore della parola chiave this non viene rimbalzato, ma viene ereditato dallo scope del genitore (questo è chiamato scoping lessicale). In questo caso particolare, la funzione freccia in questione viene passata come argomento al metodo startBtn.addEventListener(), che si trova nello scope globale. Di conseguenza, anche il this all’interno del gestore della funzione freccia è legato allo scope globale – cioè all’oggetto Window.

Quindi, se volete che this faccia riferimento al pulsante di avvio nel programma, l’approccio corretto è usare una funzione regolare, non una funzione freccia.

La prossima cosa da notare nella demo sopra è il codice all’interno del metodo .setInterval(). Anche qui troverete una funzione anonima, ma questa volta è una funzione freccia. Perché?

Vedete quale sarebbe il valore di this se usaste una funzione regolare:

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

Sarebbe l’elemento button? Assolutamente no. Sarebbe l’oggetto Window!

In effetti, il contesto è cambiato, poiché this è ora dentro una funzione unbound o globale che viene passata come argomento a .setInterval(). Pertanto, anche il valore della parola chiave this è cambiato, poiché ora è legato all’ambito globale. Un trucco comune in questa situazione è stato quello di includere un’altra variabile per memorizzare il valore della parola chiave this in modo che continui a riferirsi all’elemento previsto – in questo caso, l’elemento button:

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

Puoi anche usare .bind() per risolvere il problema:

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

Con le funzioni freccia, il problema scompare completamente. Ecco qual è il valore di this quando usi una funzione freccia:

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

Questa volta, la console registra il pulsante, che è esattamente ciò che serve. Infatti, il programma sta per cambiare il testo del pulsante, quindi ha bisogno che this faccia riferimento all’elemento button:

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

Le funzioni freccia non hanno un proprio contesto this. Ereditano il valore di this dal genitore, ed è a causa di questa caratteristica che sono un’ottima scelta in situazioni come quella sopra.

Le funzioni freccia non sono sempre lo strumento giusto per il lavoro

Le funzioni freccia non sono solo un nuovo modo di scrivere funzioni in JavaScript. Hanno le loro stranezze e limitazioni, il che significa che ci sono casi in cui non si vuole usare una funzione freccia. Il gestore del click nella demo precedente è un caso esemplare, ma non è l’unico. Esaminiamone alcuni altri.

Funzioni freccia come metodi di oggetti

Le funzioni freccia non funzionano bene come metodi su oggetti. Ecco un esempio. Considerate questo oggetto netflixSeries, che ha alcune proprietà e un paio di metodi. Chiamando console.log(netflixSeries.getLikes()) si dovrebbe stampare un messaggio con l’attuale numero di like, e chiamando console.log(netflixSeries.addLike()) si dovrebbe aumentare il numero di like di uno e poi stampare il nuovo valore con un messaggio di ringraziamento sulla console:

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` } }

Invece, chiamando il metodo .getLikes() si ottiene “undefined has NaN likes”, e chiamando il metodo .addLike() si ottiene “Thank you for liking undefined, which now has NaN likes”. Quindi, this.title e this.likes non riescono a fare riferimento alle proprietà dell’oggetto title e likes rispettivamente.

Ancora una volta, il problema sta nello scoping lessicale delle funzioni freccia. Il this all’interno del metodo dell’oggetto fa riferimento all’ambito del genitore, che in questo caso è l’oggetto Window, non il genitore stesso – cioè, non l’oggetto netflixSeries.

La soluzione, ovviamente, è usare una funzione regolare:

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

Funzioni Freccia con librerie di terze parti

Un altro inconveniente di cui essere consapevoli è che le librerie di terze parti spesso legano le chiamate ai metodi in modo che il valore this punti a qualcosa di utile.

Per esempio, all’interno di un gestore di eventi jQuery, this vi darà accesso all’elemento DOM a cui il gestore è stato legato:

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

Ma se usiamo una funzione freccia – che, come abbiamo visto, non ha un proprio contesto this – otteniamo risultati inaspettati:

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

Ecco un altro esempio usando Vue.js:

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

Nell’hook created, this è legato all’istanza di Vue, quindi viene visualizzato il messaggio “Hello, World!

Se usiamo una funzione freccia, tuttavia, this punterà all’ambito genitore, che non ha una proprietà message:

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

Arrow Functions Have No arguments Object

A volte, potresti aver bisogno di creare una funzione con un numero indefinito di parametri. Per esempio, diciamo che volete creare una funzione che elenchi le vostre serie preferite di Netflix ordinate per preferenza. Tuttavia, non sapete ancora quante serie intendete includere. JavaScript rende disponibile l’oggetto arguments, un oggetto simile a un array (non un array completo, però), che memorizza i valori che vengono passati alla funzione quando viene chiamata.

Prova ad implementare questa funzionalità usando una funzione freccia:

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

Quando chiamate la funzione, otterrete il seguente messaggio di errore: Uncaught ReferenceError: arguments is not defined. Ciò significa che l’oggetto arguments non è disponibile nelle funzioni freccia. In effetti, sostituendo la funzione freccia con una funzione normale si ottiene il trucco:

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: 

Quindi, se avete bisogno dell’oggetto arguments, non potete usare le funzioni freccia.

Ma se volete davvero usare una funzione freccia per replicare la stessa funzionalità? Una cosa che potete fare è usare i parametri di riposo ES6 (...). Ecco come potreste riscrivere la vostra funzione:

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

Conclusione

Utilizzando le funzioni freccia, potete scrivere concisi one-liner con ritorno implicito e finalmente dimenticare i vecchi hack per risolvere il binding della parola chiave this in JavaScript. Le funzioni freccia funzionano benissimo anche con metodi di array come .map(), .sort(), .forEach(), .filter() e .reduce(). Ma ricordate, le funzioni freccia non sostituiscono le normali funzioni JS, e non possono essere usate per tutto.

Se hai qualche domanda sulle funzioni freccia, o hai bisogno di aiuto per ottenerle nel modo giusto, ti consiglio di fermarti negli amichevoli forum di SitePoint, dove ci sono molti programmatori esperti pronti ad aiutarti.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.