
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:
- Chiamata di metodo
objet.fonction():thisèobjet. - Chiamata semplice
fonction():thisèundefinedin modalità strict (moduli, classi), l’oggetto globalewindowin modalità non strict. - Chiamata con
new:thisè l’oggetto in corso di costruzione. - Chiamata tramite
call,applyo una funzione legata conbind: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:
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.
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 è ripristinatoUn uso classico: riutilizzare un metodo degli array su qualcosa che array non è.
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.
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 arrayDa 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.
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
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(); // 2Lo stesso schema vale per i timer e per gli eventi del browser:
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.
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:
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:
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 chiamataRovescio 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:
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
bindoppure dichiaralo come proprietà arrow. - Un listener di eventi che non modifica lo stato:
thisè l’elemento DOM, non il tuo oggetto. Stesso rimedio. - Un
bindche sembra non fare nulla: la funzione è una arrow function. Togli ilbind, è inutile. removeEventListenerche non rimuove nulla: ognibindcrea 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
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.this proprio: call, apply e bind non lo modificano. Contano solo gli argomenti passati da bind.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).f.call(obj, [1, 2]) passa un solo argomento, l'array. Per espandere un array serve apply, oppure f.call(obj, ...tableau).

