Funzioni Arrow e `this` in JavaScript: una guida completa

  • Il valore di this In JavaScript, dipende da come viene richiamata la funzione, variando tra contesto globale, metodi di oggetto e utilizzo della modalità rigorosa.
  • Le funzioni freccia non creano le proprie thisInvece, ereditano lessicalmente la terminologia del dominio in cui sono stati definiti, il che evita molti problemi nei callback.
  • Si consiglia di utilizzare le funzioni freccia nei callback e nei metodi array, ma evitarle come metodi di oggetti, costruttori o gestori di eventi DOM che richiedono this dinamico.

Illustrazione della freccia e di queste funzioni in JavaScript

Se hai programmato in JavaScript per un po', riconoscerai sicuramente la parola chiave Questo ti ha causato più di un mal di testaE da quando le funzioni freccia sono apparse in ES6, le cose sono diventate ancora più complicate... o più semplici, a seconda del punto di vista.

In questo articolo daremo un'occhiata più da vicino a come funziona Ciò vale per le funzioni tradizionali e per le funzioni freccia.Perché a volte sembra puntare a un oggetto specifico e altre volte all'oggetto globale, e in quali situazioni ha senso usare le funzioni freccia e in quali situazioni è meglio evitarle?

Qué è esattamente this in JavaScript

La parola riservata this È un riferimento al contesto di esecuzione della funzione attualmente in esecuzione. A differenza di altri linguaggi, in JavaScript la decisione non si basa su dove è definita la funzione, ma piuttosto sulla sua esecuzione. come invocare.

Ciò significa che la stessa funzione può essere chiamata in modi diversi e in ognuno di essi, this può indicare un oggetto diversoNon è qualcosa che puoi cambiare con un incarico diretto (non puoi farlo this = algo), ma è possibile influenzarlo con meccanismi specifici come call, apply y bind.

Inoltre, il loro comportamento varia tra modalità rigorosa e modalità non rigorosaIn modalità non rigorosa, se si chiama una funzione "bare" (senza un oggetto davanti), this Di solito è l'oggetto globale (nel browser, window), mentre in modalità rigorosa può essere undefinedQuesta distinzione è importante quando si confrontano esempi di codice provenienti da fonti diverse.

Questo nel contesto globale e nelle funzioni normali

Nei browser, quando non ci si trova all'interno di alcun modulo o funzione, il contesto globale è l'oggetto windowe lì this indica quell'oggettoCioè, se digiti quanto segue nella console:

console.log(this === window); // true en un entorno de navegador no estricto

All’interno di una funzione dichiarata in modo “classico” (funzione normale), il valore di this Dipende da come si chiama quella funzione.Se lo si invoca senza un oggetto precedente, in modalità non rigorosa this Di solito è quello globale e, a rigor di termini, sarà undefinedEcco perché a volte, quando si sposta il codice da un sito all'altro, Non è più ciò che ti aspettavi..

Questo nei metodi oggetto definiti con funzioni normali

Quando si definisce un metodo su un oggetto utilizzando la sintassi tradizionale, this all'interno del metodo, riferimento all'oggetto stesso da cui è stato invocato quel metodo.

Ad esempio, se hai qualcosa come:

const obj = {
  speak() {
    console.log(this);
  }
};
obj.speak();

La chiamata obj.speak() marche this all'interno speak essere quello objQuesto è il comportamento che di solito le persone si aspettano intuitivamente: il metodo parla "per conto" dell'oggetto.

Se si utilizza una funzione classica invece della sintassi abbreviata, l'effetto è lo stesso, perché La chiave sta nel modo in cui il metodo viene invocatoNon importa se hai usato l'abbreviazione del metodo o la parola chiave function all'interno dell'oggetto.

Questo nei metodi definiti con funzioni freccia

Le cose cambiano quando si definisce il metodo con una funzione freccia. Qualcosa del tipo:

const obj2 = {
  speak: () => {
    console.log(this);
  }
};
obj2.speak();

In questo caso, quando si esegue obj2.speak() lo vedrai this Non è più obj2ma il contesto lessicale esterno a quell'oggetto, che in uno script di browser classico è solitamente l'oggetto globale window.

La prima volta che lo vedi, questo può sembrare sconcertante, perché ti aspetti che un metodo dell'oggetto punti all'oggetto stesso. Tuttavia, le funzioni freccia non creano le proprie thisEreditano il valore di this dell'ambito in cui sono stati definiti. Se tale ambito è globale, ereditano l'ambito globale; se è un altro, erediteranno quell'altro ambito.

Pertanto, una raccomandazione ripetuta frequentemente nella documentazione moderna è: Non utilizzare le funzioni freccia come metodi oggetto quando ne hai bisogno this mirare a quell'oggetto.

Ambito lessicale di this funzioni freccia

La differenza fondamentale tra le funzioni normali e le funzioni freccia è che queste ultime avere un collegamento lessicale per thisIn parole povere: non decidono loro this non quando si chiamano, ma quando creare.

Immagina questo esempio:

const obj3 = {
  speak() {
    (() => {
      console.log(this);
    })();
  }
};
obj3.speak();

Qui potrebbe sembrare che, come all'interno speak eseguiamo una funzione freccia, Questo dovrebbe "resettarsi" al livello globaleMa accade esattamente l'opposto: la funzione freccia cattura l' this della funzione che lo circondache in questo caso è il metodo speak invocato come obj3.speak()Pertanto, il valore di this Quello mostrato sulla console è quello da obj3.

Vale a dire, le funzioni freccia non hanno una propria thisma piuttosto riutilizzare quello dei loro immediati dintorniCiò è incredibilmente utile nei callback annidati, nei timer, nelle promesse e in qualsiasi altro luogo in cui, con le funzioni classiche, dovevi lottare con .bind o con trucchi come const that = this;.

Esempi pratici di perdita e conservazione di this

Uno dei problemi classici di JavaScript è che, quando si definisce una funzione all'interno di un metodo, si perde il riferimento a this che puntava all'oggetto e ti ritrovi con quello globale o con undefined.

Prendiamo il caso tipico di utilizzo setTimeout all'interno di un metodo di un oggetto con una funzione tradizionale:

const persona = {
  nombre: 'Agustin',
  decirNombre: function() {
    setTimeout(function() {
      console.log(this.nombre);
    }, 3000);
  }
};
persona.decirNombre(); // Muestra undefined

qui questo all'interno della funzione passata a setTimeout Non è più l'oggetto personaTale funzione di callback viene eseguita nel contesto globale (in un browser, window), così this.nombre Prova a leggere una proprietà nel globale, che non esiste, e finisce per essere undefined.

Prima che esistessero le funzioni freccia, una soluzione comune era quella di memorizzare il valore di this in una variabile ausiliaria per "trascinarlo" nella funzione:

const persona = {
  nombre: 'Agustin',
  decirNombre: function() {
    let that = this; // aquí this es persona
    setTimeout(function() {
      console.log(that.nombre);
    }, 3000);
  }
};

Grazie a questa variabile, viene mantenuto il riferimento corretto all'oggetto. Ma è un trucco un po' antiestetico e ripetitivo. Con le funzioni freccia, questo problema è notevolmente semplificato:

const persona = {
  nombre: 'Agustin',
  decirNombre: function() {
    setTimeout(() => {
      console.log(this.nombre);
    }, 3000);
  }
};

Qui la funzione freccia non crea la propria this, quindi eredita il this del metodo decirNombrequal è l'oggetto personaIl risultato: “Agustin” viene visualizzato correttamente senza la necessità di variabili intermedie o .bind.

chiama, applica e associa: controllo del valore di this

Oltre al modo "naturale" di impostare il contesto con una chiamata al metodo, JavaScript ci fornisce gli strumenti per forzare il valore di this nelle funzioni normali: call, apply y bind.

Metodi call() y apply() Invocano immediatamente la funzione, consentendo di passare l'oggetto che si desidera utilizzare come this. La differenza è questa call riceve gli argomenti uno per uno, mentre apply Vengono ricevuti in un array. bind(), invece, restituisce una nuova funzione con l' this “allegato” al valore che hai indicatocosì potrai chiamarla più tardi, quando ti farà comodo.

Con le funzioni freccia, tuttavia, questi metodi non sono utili per modificare this perché il suo valore è lessicalmente collegato. Puoi usare call, apply o bind per passare argomenti, ma non per modificare il contesto delle funzioni freccia, il che rappresenta una differenza molto importante rispetto alle funzioni normali.

Sintassi di base delle funzioni freccia

Oltre il comportamento di thisLe funzioni freccia forniscono un sintassi più compatta ed espressiva per molte situazioni. La forma generale è:

(arg1, arg2, ..., argN) => expresion

Questo modulo restituisce automaticamente il risultato dell'espressione a destra della freccia, quindi Non c'è bisogno di scrivere la parola return quando hai una sola semplice espressione.

Alcuni punti comuni della sintassi:

  • Senza parametri:
    () => 42 o anche _ => 42 se non ti interessa il nome dell'argomento.
  • Con un singolo parametro:
    Le parentesi sono facoltative; puoi scrivere x => x * 2 o (x) => x * 2.
  • Con più parametri:
    Le parentesi sono obbligatorie: (x, y) => x + y.

Quando hai bisogno di più istruzioni, puoi usare corpo del blocco con le chiavi:

const sumar = (x, y) => {
  const resultado = x + y;
  return resultado;
};

In questo caso, poiché ci sono le chiavi, Non c'è più alcun ritorno implicito; se non metti returnla funzione restituirà undefinedCiò vale sia per le funzioni freccia che per quelle tradizionali.

Restituisci oggetti letterali con funzioni freccia

C'è un piccolo ma molto comune dettaglio sintattico: quando una funzione freccia restituisce un oggetto letterale direttamenteÈ necessario racchiuderlo tra parentesi in modo che l'interprete non lo scambi per un blocco.

Ad esempio:

x => ({ y: x })

Senza queste parentesi, JavaScript interpreterebbe le parentesi graffe come l'inizio del corpo della funzione, non come un oggetto. È un trucco semplice, ma se lo si dimentica, può causare molti errori stupidi.

Funzioni freccia: anonime e senza prototipo

Le funzioni freccia sono sintatticamente anonimoNon hanno nomi propri, il che può complicare un po' le cose. messaggi di debug e di erroreperché nella traccia non vedi direttamente l'identificatore della funzione, a meno che non lo abbia assegnato a una costante con un nome riconoscibile.

Inoltre, le funzioni della freccia Non possiedono proprietà prototype e non possono essere utilizzate come imprese ediliSe provi a evocarli con newRiceverai un errore. Per creare oggetti utilizzando costruttori o classi, devi comunque utilizzare le normali funzioni o la sintassi. class.

Un'altra conseguenza è che Non sono adatti per modelli che richiedono autoreferenzialità interna, come alcune forme di ricorsione o gestori di eventi che devono annullare l'iscrizione utilizzando this oppure il nome della funzione stessa.

Dove brillano le funzioni freccia

La grande forza delle funzioni freccia è proprio la loro collegamento lessicale di thisSono ideali nelle situazioni in cui si desidera che il callback passato a un'altra funzione mantenga il this della zona circostante.

Ad esempio, in un oggetto con un metodo che avvia un timer e deve continuare ad accedere proprietà dell'oggetto stesso utilizzando this:

const contador = {
  id: 42,
  iniciar() {
    setTimeout(() => {
      console.log(this.id); // this es contador
    }, 1000);
  }
};

In ES5 era comune dover mettere .bind(this) alla richiamata o al salvataggio this in un'altra variabile. Con le funzioni freccia, Il codice diventa più pulito e più vicino all'intento reale..

Sono anche molto pratici con metodi array come map, filter, reduce e compagnia, perché ridurre il rumore sintattico quando la logica della funzione è breve:

const numeros = [1, 2, 3];
const dobles = numeros.map(n => n * 2);

Se usate con parsimonia, queste forme compatte rendono più facile seguire a colpo d'occhio il flusso di dati.

Quando evitare le funzioni freccia

Sebbene le funzioni freccia siano molto utili, non sostituiscono le funzioni normali. Esistono diversi casi evidenti in cui È meglio non usarli.:

  • Metodi oggetto che dipendono da this:
    Se si definisce un metodo come saltos: () => { this.vidas--; } all'interno di un oggetto gato, this Non punterà al gatto, ma all'ambiente esterno e la proprietà non verrà aggiornata come previsto.
  • Callback di eventi DOM che necessitano di un this dinamico:
    In un gestore come boton.addEventListener('click', () => { this.classList.toggle('on'); });, this Probabilmente non sarà il pulsante premuto, ma il contesto più elevato a generare un errore di tipo.
  • Costruttori o funzioni che necessitano prototype:
    Poiché non può essere utilizzato con newLe funzioni freccia non sono adatte alla creazione di istanze o di modelli basati su prototipi.

In tutti questi casi, un La funzione normale rimane lo strumento appropriato perché permette che this È collegato dinamicamente al modo in cui si richiama la funzione.

Se ti abitui a scegliere consapevolmente tra la funzione normale e la funzione freccia a seconda di ciò di cui hai bisogno this e dal contesto, Il tuo codice sarà più prevedibile e leggibile. per chiunque lo conservi in ​​seguito.

In definitiva, comprendere come il valore di this in JavaScript e come le funzioni freccia ereditano quel valore La chiave per smettere di lottare con risultati inaspettati, sfruttare lo zucchero sintattico di ES6 e scrivere metodi, callback e gestori di eventi che facciano esattamente ciò che avevi in ​​mente è comprendere l'ambito lessicale in cui vengono creati.