Le funzioni in JavaScript: declaration, expression, arrow
Hoisting, this binding, arguments vs rest parameters
In JavaScript esistono almeno tre modi per scrivere una funzione: function declaration, function expression e arrow function. Sembrano intercambiabili e per il 90% dei casi lo sono, ma le differenze che restano riguardano tre cose molto pratiche: quando puoi chiamarle, chi è this al loro interno, e come ricevono gli argomenti extra.
Possiamo pensare a tre modi diversi di assumere un dipendente. Il primo (declaration) è come un impiegato già registrato in azienda fin dal primo giorno, anche prima che tu «lo cerchi» nell'organigramma. Il secondo e il terzo (expression, arrow) sono collaboratori che iniziano a esistere solo dal momento esatto in cui li assumi, richiamarli prima non serve perchè è come se non esistessero.
1. Le tre sintassi, una accanto all'altra
// Function declaration
function somma(a, b) {
return a + b;
}
// Function expression (anonima, assegnata a una variabile)
const somma2 = function (a, b) {
return a + b;
};
// Arrow function
const somma3 = (a, b) => a + b;
Una function declaration inizia con la parola chiave function ed è un'istruzione a sé stante. Una function expression è invece un valore, una funzione creata «al volo» e assegnata a qualcosa, spesso a una variabile. L'arrow function è una sintassi più compatta per esprimere lo stesso concetto di function expression, con la freccia => al posto della parola function.
Fonte: MDN Web Docs: Functions e Arrow function expressions.
2. Hoisting: solo le declaration «esistono già»
MDN lo dice senza ambiguità: l'hoisting delle funzioni funziona solo con le function declaration, non con le function expression (e, di conseguenza, nemmeno con le arrow function, che sono sempre expression).
saluta();
// funziona: "Ciao!" -- la declaration e' gia' disponibile
function saluta() {
console.log("Ciao!");
}
saluta2();
// TypeError: saluta2 is not a function errore generato perchè la funzione viene invocata prima di essere creata
const saluta2 = function () {
console.log("Ciao!");
};
3. this binding: chi è "io" dentro la funzione
Le function declaration e le function expression hanno un proprio this, il cui valore dipende da come la funzione viene chiamata (come metodo di un oggetto, da sola, con call()/apply()/bind()).
Le arrow function sono radicalmente diverse: non hanno un proprio this. Lo ereditano lessicalmente dal contesto in cui sono scritte, e non cambia mai, qualunque sia il modo in cui vengono chiamate.
const timer = {
secondi: 0,
avvia: function () {
setInterval(function () {
this.secondi++;
// BUG: qui "this" NON fa riferimento all'oggetto timer
}, 1000);
},
};
const timerCorretto = {
secondi: 0,
avvia: function () {
setInterval(() => {
this.secondi++;
// OK: l'arrow function eredita il "this" di avvia()
}, 1000);
},
};
Una funzione normale è come un attore che cambia personaggio a seconda del copione che gli dai in mano quella sera: il «this» dipende da chi lo chiama sul palco. Un'arrow function è invece un attore che recita sempre sé stesso, ovunque lo metti: mantiene l'identità del contesto in cui è nato, senza travestirsi mai.
Per questo motivo, MDN sconsiglia di usare le arrow function come metodi di un oggetto quando ti serve che this punti all'oggetto stesso: in quel caso non cambia mai, e il risultato è quasi sempre un bug.
4. arguments vs rest parameters: raccogliere gli argomenti extra
Le funzioni tradizionali hanno accesso a un oggetto speciale, arguments, con tutti i valori passati alla chiamata, anche quelli senza un nome di parametro dichiarato. Ma MDN segnala tre differenze chiave rispetto ai rest parameters (...nome) introdotti con ES6:
- i rest parameters raccolgono solo gli argomenti non ancora assegnati a un nome; arguments li contiene tutti
- arguments è array-like, non un vero array, niente .map(), .filter() diretti; i rest parameters sono un vero Array
- le arrow function non hanno un proprio arguments: all'interno di una arrow, quel nome fa riferimento allo scope che la contiene: un motivo in più per usare sempre i rest parameters
// "arguments" è array-like: non ha direttamente i metodi degli array
function sommaVecchio() {
return Array.prototype.slice
.call(arguments)
.reduce((a, b) => a + b, 0);
}
// Il parametro rest è già un vero array
function sommaModerno(...numeri) {
return numeri.reduce((a, b) => a + b, 0);
}
Fonte: MDN Web Docs: «Rest parameters»: differenze tra arguments object e rest parameters.
In sintesi
| Function declaration | Expression / Arrow | |
|---|---|---|
| Hoisting | Sì, chiamabile prima della riga di definizione | No, deve essere già stata assegnata |
| this | Proprio this, dipende da come viene chiamata | Nessun this proprio: eredita quello del contesto |
| arguments | Disponibile, ma array-like (non un vero array) | Non esiste: usa i rest parameters |
| Come costruttore | Utilizzabile con new | Mai: new lancia TypeError |
Tre strumenti, tre casi d'uso: la declaration per funzioni che ti servono ovunque nel file, l'expression quando la funzione è un valore da passare in giro, l'arrow quando ti serve compattezza e this stabile, non quando ti serve un metodo su un oggetto.
Per approfondire
- Spread e Rest operator (...): stesso simbolo, lavori opposti: i rest parameters visti qui, insieme al loro gemello: lo spread.
- Template literals: interpolazione, tagged templates, multiline: le tagged template sono funzioni chiamate in un modo particolare.
Commenti (0)
Registrati per commentare, fare domande e proporre l'argomento della prossima pillola (oppure accedi se hai già un account).
Nessun commento. Sii il primo a scrivere qualcosa.