javascript Intermediate 4 min

Il this in JavaScript: regole di binding

call, apply, bind: default, implicit, explicit, new binding, e le arrow function

· 767 parole

La stessa funzione, chiamata in due punti diversi del codice, può avere un this completamente diverso. In una normale chiamata di funzione, this viene passato implicitamente attraverso il prefisso della chiamata, cioè come chiami la funzione, non dove l'hai scritta.

«Il capo» non è sempre la stessa persona: dipende da dove ti trovi quando lo dici. In ufficio è il tuo manager; a casa di un amico, potrebbe essere suo padre che scherzando si definisce così. La parola this funziona allo stesso modo: il suo significato dipende dal contesto della chiamata, non da dove la frase è scritta sul copione.

1. Default binding: la chiamata semplice, senza proprietario

Quando chiami una funzione da sola, senza nessun oggetto davanti, this punta all'oggetto globale o resta undefined in modalità strict.

function saluta() {
    console.log(this);
}

saluta(); // L'oggetto globale (window/globalThis), oppure undefined in strict mode

Fonte: MDN Web Docs: «this»: in una chiamata di funzione normale (non strict mode), this si riferisce all'oggetto globale se non è impostato dalla chiamata stessa.

2. Implicit binding: chi c'è prima del punto

Chiamando una funzione come metodo di un oggetto, this diventa quell'oggetto: quello scritto prima del punto.

const utente = {
    nome: "Ada",

    saluta() {
        console.log(`Ciao, sono ${this.nome}`);
    },
};

utente.saluta(); // "Ciao, sono Ada" -- this è utente

Attenzione all'insidia più comune: se stacchi il metodo dall'oggetto (assegnandolo a una variabile) e lo chiami da solo, torna a valere il default binding, non è più un metodo, è una chiamata semplice.

const funzioneStaccata = utente.saluta;

funzioneStaccata();
// "Ciao, sono undefined" -- this non è più utente!

3. Explicit binding: decidere tu, con call, apply, bind

Tre metodi permettono di impostare esplicitamente quale sarà this, a prescindere da come la funzione è scritta o normalmente chiamata.

function saluta() {
    console.log(`Ciao, sono ${this.nome}`);
}

const persona = {
    nome: "Grace",
};

saluta.call(persona);
// Esegue subito, this = persona

saluta.apply(persona);
// Uguale a call, ma riceve gli argomenti in un array

const salutaGrace = saluta.bind(persona);

salutaGrace();
// Esegue quando vuoi, this resta sempre persona

La differenza chiave: call()/apply() eseguono subito la funzione; bind() crea invece una nuova funzione con this fissato per sempre, utile quando devi passare un metodo come callback, senza perderne il contesto.

Fonte: MDN Web Docs: «this»: con call() e apply() puoi passare il valore di this come un parametro esplicito; «Function.prototype.bind()»: crea una nuova funzione con un valore di this specifico, che non cambia indipendentemente da come la funzione bound viene poi chiamata.

4. New binding: this è l'oggetto appena nato

Quando chiami una funzione con new, MDN è preciso: viene creato un nuovo oggetto, e tutti i riferimenti a this dentro la funzione puntano ora a quel nuovo oggetto.

function Auto(marca) {
    this.marca = marca;
}

const miaAuto = new Auto("Fiat");

console.log(miaAuto.marca);
// "Fiat" -- this era il nuovo oggetto creato

Questa regola vince su tutte le altre: persino bind() non riesce a fissare this se la funzione risultante viene poi chiamata con new: in quel caso, il valore passato a bind() viene ignorato.

Fonte: MDN Web Docs: «new»: la funzione costruttore viene eseguita con this legato al nuovo oggetto creato. «Function.prototype.bind()»: il valore this passato a bind() viene ignorato se la funzione risultante viene costruita con new.

5. Arrow function: this che non cambia mai

Le arrow function seguono una regola completamente diversa dalle quattro precedenti. MDN è netto: non hanno un proprio binding di this: lo ereditano dallo scope in cui sono state scritte, e quel valore non cambia mai, indipendentemente da come la arrow function viene poi invocata.

const contatore = {
    valore: 0,

    incrementaConRitardo() {
        setTimeout(() => {
            this.valore++;
            // `this` è ereditato da incrementaConRitardo,
            // cioè è contatore

            console.log(this.valore);
        }, 1000);
    },
};

contatore.incrementaConRitardo();
// Dopo 1 secondo, stampa 1

Proprio per questo, call(), apply() e bind() non hanno alcun effetto su una arrow function e usarla con new genera direttamente un errore: le arrow function non possono essere costruttori.

Fonte: MDN Web Docs: «Arrow function expressions»: le arrow function stabiliscono this in base allo scope in cui sono definite, e il valore non cambia in base a come vengono invocate; call/apply/bind non sono utili su di esse; non possono essere usate come costruttori e generano TypeError con new.

In sintesi

Regola Chi decide this Esempio
Default Nessuno: oggetto globale o undefined funzione()
Implicit L'oggetto prima del punto oggetto.metodo()
Explicit Chi chiami esplicitamente con call/apply/bind fn.call(obj)
New Il nuovo oggetto creato (vince su tutte) new Costruttore()
Arrow function Ereditato dallo scope, mai le regole sopra () => this.x

Comprendere this significa, in ogni riga di codice, farsi una sola domanda: «come sto chiamando questa funzione, in questo punto preciso?». La risposta a quella domanda decide sempre chi è this.

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