Newsletter

bind(), call() e apply() in JavaScript: capire e controllare this

Perché this cambia valore a seconda di come si chiama una funzione, e come call(), apply() e bind() lo fissano: esempi eseguiti, casi dei callback e delle classi, confronto con le arrow function.

bind(), call() e apply() in JavaScript: cambiare this
Risposta rapida

call() e apply() eseguono subito una funzione con un this scelto da te; call riceve gli argomenti uno per uno, apply li riceve dentro un array. bind() non esegue nulla: restituisce una nuova funzione il cui this (ed eventualmente i primi argomenti) resta fissato per sempre. Serve quando un metodo viene passato come callback e perde il suo oggetto.

Tutte le funzioni JavaScript hanno tre metodi, call(), apply() e bind(), che servono alla stessa causa: decidere quanto vale this nel momento in cui la funzione viene eseguita. Per capire a che cosa servono bisogna prima capire il problema che risolvono: in JavaScript this non dipende dal punto in cui una funzione è scritta, ma dal modo in cui viene chiamata.

Il problema: this cambia a seconda della chiamata

Quattro regole bastano a prevedere this in quasi tutti i casi:

  1. Chiamata di metodo objet.fonction(): this è objet.
  2. Chiamata semplice fonction(): this è undefined in modalità strict (moduli, classi), l’oggetto globale window in modalità non strict.
  3. Chiamata con new: this è l’oggetto in corso di costruzione.
  4. Chiamata tramite call, apply o una funzione legata con bind: this è quello che hai deciso tu.

Le arrow function sfuggono a queste regole: non hanno un this proprio e mantengono quello del codice che le circonda.

Il caso che fa male nella pratica è il passaggio dalla regola 1 alla regola 2:

probleme.js
const person = {
  name: 'Sarah',
  hello() {
    return `Bonjour ${this.name}`;
  },
};

person.hello();            // 'Bonjour Sarah': chiamata di metodo, this = person

const detached = person.hello;
detached();                // TypeError in modalità strict: this vale undefined
                           // ('Bonjour undefined' oppure 'Bonjour ' in modalità non strict)

La funzione non è cambiata. È cambiato solo il modo di chiamarla, e con esso this. È esattamente quello che succede quando si passa un metodo a setTimeout, ad addEventListener o a map.

call(): chiamare con un this scelto

fonction.call(thisArg, arg1, arg2, …) esegue subito la funzione, con this uguale a thisArg e gli argomenti passati uno per uno.

call.js
function presenter(age, ville) {
  return `${this.name}, ${age} ans, ${ville}`;
}

const person = { name: 'Sarah' };

presenter.call(person, 30, 'Lille');           // 'Sarah, 30 ans, Lille'
presenter.call({ name: 'Léa' }, 25, 'Paris');   // 'Léa, 25 ans, Paris'

const detached = person.hello;                  // il metodo dell'esempio precedente
detached.call(person);                          // 'Bonjour Sarah': this è ripristinato

Un uso classico: riutilizzare un metodo degli array su qualcosa che array non è.

javascript
Array.prototype.slice.call('abc');   // ['a', 'b', 'c']
// Oggi si scrive piuttosto:
Array.from('abc');                   // ['a', 'b', 'c']

apply(): la stessa cosa, argomenti in un array

fonction.apply(thisArg, [arg1, arg2, …]) fa esattamente quello che fa call, ma riceve gli argomenti sotto forma di array. Comodo quando arrivano già raggruppati.

apply.js
presenter.apply(person, [30, 'Lille']);   // 'Sarah, 30 ans, Lille'

const notes = [3, 9, 4];
Math.max.apply(null, notes);   // 9: Math.max si aspetta argomenti separati, non un array

Da ES2015 l’operatore spread rende apply raramente necessario: Math.max(...notes) e presenter.call(person, ...args) fanno lo stesso lavoro restando leggibili. apply si incontra soprattutto nel codice scritto prima.

bind(): fissare this per dopo

Là dove call e apply eseguono subito, bind non esegue nulla. Restituisce una nuova funzione il cui this è fissato definitivamente. È lo strumento dei callback: la funzione si prepara adesso, qualcun altro la chiamerà più tardi.

bind.js
const bound = person.hello.bind(person);

bound();                          // 'Bonjour Sarah', qualunque sia il contesto di chiamata
bound.call({ name: 'X' });        // 'Bonjour Sarah': vince bind, call non può più cambiare this
bound.name;                       // 'bound hello'

Il caso tipico: un metodo passato come callback

compteur.js
class Compteur {
  constructor() {
    this.n = 0;
  }
  incrementer() {
    this.n += 1;
    return this.n;
  }
}

const compteur = new Compteur();

// Senza bind: il metodo è staccato dal suo oggetto
const inc = compteur.incrementer;
inc();   // TypeError: Cannot read properties of undefined (reading 'n')

// Con bind: this è fissato a compteur
const incLie = compteur.incrementer.bind(compteur);
incLie();   // 1
incLie();   // 2

Lo stesso schema vale per i timer e per gli eventi del browser:

javascript
setTimeout(compteur.incrementer.bind(compteur), 1000);
bouton.addEventListener('click', compteur.incrementer.bind(compteur));

// Per poter rimuovere il listener in seguito, conserva il riferimento legato:
const onClick = compteur.incrementer.bind(compteur);
bouton.addEventListener('click', onClick);
bouton.removeEventListener('click', onClick);

Senza bind, addEventListener chiamerebbe incrementer con this uguale al pulsante cliccato: this.n sarebbe undefined e il contatore non conterebbe nulla.

bind() per l’applicazione parziale

Anche gli argomenti passati a bind dopo this restano fissati. La funzione restituita aspetta i successivi.

partiel.js
const presenterSarah = presenter.bind(person, 30);
presenterSarah('Lille');   // 'Sarah, 30 ans, Lille': resta da fornire solo l'ultimo argomento

const add = (a, b) => a + b;
const add5 = add.bind(null, 5);   // this non serve: basta null
add5(3);                          // 8

È un modo semplice di specializzare una funzione generica senza scrivere una funzione wrapper.

Arrow function: quando bind diventa inutile

Una arrow function cattura il this del punto in cui è scritta e poi lo ignora. Due conseguenze.

Primo, call, apply e bind non possono cambiare il suo this:

javascript
const arrow = () => this;
arrow.call({ a: 1 });   // il this del modulo, non { a: 1 }

Secondo, ed è l’uso corrente, una arrow function dichiarata dentro un metodo o una classe conserva il this giusto senza bind:

fleche.js
class Compteur {
  n = 0;

  // Proprietà arrow: this è catturato una volta per tutte
  incrementer = () => {
    this.n += 1;
    return this.n;
  };
}

const c = new Compteur();
const inc = c.incrementer;
inc();   // 1: funziona senza bind

setTimeout(() => c.incrementer(), 1000);   // oppure una arrow function alla chiamata

Rovescio della medaglia: una proprietà arrow viene ricreata per ogni istanza, mentre un metodo classico è condiviso dal prototype. Per qualche oggetto non cambia nulla; per migliaia di istanze, il metodo legato una volta sola nel costruttore (this.incrementer = this.incrementer.bind(this)) fa risparmiare memoria.

this passato come secondo argomento

Diversi metodi degli array accettano direttamente il valore di this da usare nel callback, il che evita un bind:

javascript
const config = { k: 10 };
[1, 2, 3].map(function (x) { return this.k * x; }, config);   // [10, 20, 30]

forEach, filter, some ed every hanno lo stesso secondo argomento. Con una arrow function diventa inutile.

Tabella comparativa

call apply bind
Esegue la funzione subito subito no: restituisce una funzione
Argomenti uno per uno in un array uno per uno, fissati per le chiamate future
Uso tipico prendere in prestito un metodo espandere un array (sostituito da ...) callback, eventi, applicazione parziale
Effetto su una arrow function nessuno su this nessuno su this nessuno su this, gli argomenti sono fissati

Errori frequenti e come leggerli

  • «Cannot read properties of undefined (reading ‘n’)» in un metodo di classe: il metodo è stato staccato dalla sua istanza (regola 2). Legalo con bind oppure dichiaralo come proprietà arrow.
  • Un listener di eventi che non modifica lo stato: this è l’elemento DOM, non il tuo oggetto. Stesso rimedio.
  • Un bind che sembra non fare nulla: la funzione è una arrow function. Togli il bind, è inutile.
  • removeEventListener che non rimuove nulla: ogni bind crea una nuova funzione; bisogna passare lo stesso riferimento legato all’aggiunta e alla rimozione.

Per proseguire, le classi in JavaScript tornano sui metodi e sul prototype, e il capitolo Le funzioni del tutorial pone le basi se il vocabolario di questo articolo è ancora nuovo. In PHP la questione si pone diversamente: $this non cambia mai oggetto.

Errori frequenti

Passare un metodo come callback senza bind bouton.addEventListener('click', compteur.incrementer) chiama la funzione con this uguale al pulsante, non a compteur. Scrivi compteur.incrementer.bind(compteur) oppure una arrow function.
Credere che bind su una arrow function cambi this Una arrow function non ha un this proprio: call, apply e bind non lo modificano. Contano solo gli argomenti passati da bind.
Chiamare bind a ogni render bind crea una nuova funzione a ogni chiamata. In un ciclo o in un render ripetuto, lega una volta sola nel costruttore (oppure dichiara il metodo come proprietà arrow).
Confondere call e apply con la stessa lista di argomenti f.call(obj, [1, 2]) passa un solo argomento, l'array. Per espandere un array serve apply, oppure f.call(obj, ...tableau).

JavaScript

Damien Flandrin Sviluppatore web dal 2010, creatore di Gekkode e di Email Impact. Ogni articolo è testato su un progetto reale prima della pubblicazione. Contatti
Newsletter

I nuovi test, tutorial e progetti, via e-mail.

Test riproducibili, codice versionato, risultati datati. Mai spam.