javascript Beginner 4 min

Le funzioni in JavaScript: declaration, expression, arrow

Hoisting, this binding, arguments vs rest parameters

· 751 parole

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

Accedi o registrati per votare, salvare o commentare la pillola

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.

Pillole correlate