
call() en apply() voeren een functie meteen uit met een gekozen this, call ontvangt de argumenten één voor één, apply ontvangt ze in een array. bind() voert niets uit: het geeft een nieuwe functie terug waarvan this (en eventueel de eerste argumenten) definitief vastligt. Je gebruikt het wanneer een methode als callback wordt doorgegeven en haar object verliest.
Elke JavaScript-functie beschikt over drie methoden, call(), apply() en bind(), die hetzelfde doel dienen: bepalen wat this waard is op het moment dat de functie wordt uitgevoerd. Om te begrijpen waarvoor ze dienen, moet je eerst het probleem begrijpen dat ze oplossen: in JavaScript hangt this niet af van de plek waar een functie is geschreven, maar van de manier waarop ze wordt aangeroepen.
Het probleem: this verandert met de aanroep
Vier regels volstaan om this in bijna alle gevallen te voorspellen:
- Methodeaanroep
objet.fonction():thisisobjet. - Eenvoudige aanroep
fonction():thisisundefinedin strict mode (modules, klassen), en het globale objectwindowin non-strict mode. - Aanroep met
new:thisis het object dat wordt aangemaakt. - Aanroep via
call,applyof een functie gebonden metbind:thisis wat je hebt bepaald.
Pijlfuncties ontsnappen aan deze regels: ze hebben geen eigen this en behouden die van de omliggende code.
Het geval dat in de praktijk pijn doet, is de overgang van regel 1 naar regel 2:
const person = {
name: 'Sarah',
hello() {
return `Bonjour ${this.name}`;
},
};
person.hello(); // 'Bonjour Sarah' : methodeaanroep, this = person
const detached = person.hello;
detached(); // TypeError in strict mode: this is undefined
// ('Bonjour undefined' of 'Bonjour ' in non-strict mode)De functie is niet veranderd. Alleen de manier waarop ze wordt aangeroepen is veranderd, en this daarmee. Dat is precies wat er gebeurt als je een methode doorgeeft aan setTimeout, aan addEventListener of aan map.
call(): aanroepen met een gekozen this
fonction.call(thisArg, arg1, arg2, …) voert de functie meteen uit, met this gelijk aan thisArg en de argumenten één voor één doorgegeven.
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; // de methode uit het vorige voorbeeld
detached.call(person); // 'Bonjour Sarah': this is hersteldEen klassiek gebruik: een array-methode hergebruiken op iets dat geen array is.
Array.prototype.slice.call('abc'); // ['a', 'b', 'c']
// Tegenwoordig schrijf je het liever zo:
Array.from('abc'); // ['a', 'b', 'c']apply(): hetzelfde, maar met argumenten in een array
fonction.apply(thisArg, [arg1, arg2, …]) doet precies hetzelfde als call, maar ontvangt de argumenten als array. Handig wanneer ze al gegroepeerd binnenkomen.
presenter.apply(person, [30, 'Lille']); // 'Sarah, 30 ans, Lille'
const notes = [3, 9, 4];
Math.max.apply(null, notes); // 9: Math.max verwacht losse argumenten, geen arraySinds ES2015 maakt de spread-operator apply zelden nog noodzakelijk: Math.max(...notes) en presenter.call(person, ...args) doen hetzelfde werk en blijven leesbaar. Je komt apply vooral tegen in code die van vóór die tijd dateert.
bind(): this vastleggen voor later
Waar call en apply meteen worden uitgevoerd, voert bind niets uit. Het geeft een nieuwe functie terug waarvan this definitief vastligt. Het is het gereedschap voor callbacks: je bereidt de functie nu voor, iemand anders roept ze later aan.
const bound = person.hello.bind(person);
bound(); // 'Bonjour Sarah', ongeacht de aanroepcontext
bound.call({ name: 'X' }); // 'Bonjour Sarah': bind wint, call kan this niet meer veranderen
bound.name; // 'bound hello'Het typische geval: een methode doorgegeven als callback
class Compteur {
constructor() {
this.n = 0;
}
incrementer() {
this.n += 1;
return this.n;
}
}
const compteur = new Compteur();
// Zonder bind: de methode is losgekoppeld van haar object
const inc = compteur.incrementer;
inc(); // TypeError: Cannot read properties of undefined (reading 'n')
// Met bind: this ligt vast op compteur
const incLie = compteur.incrementer.bind(compteur);
incLie(); // 1
incLie(); // 2Hetzelfde patroon geldt voor timers en browserevents:
setTimeout(compteur.incrementer.bind(compteur), 1000);
bouton.addEventListener('click', compteur.incrementer.bind(compteur));
// Om de listener later te kunnen verwijderen, bewaar je de gebonden referentie:
const onClick = compteur.incrementer.bind(compteur);
bouton.addEventListener('click', onClick);
bouton.removeEventListener('click', onClick);Zonder bind zou addEventListener incrementer aanroepen met this gelijk aan de aangeklikte knop: this.n zou undefined zijn en de teller zou niets tellen.
bind() voor partiële toepassing
De argumenten die na this aan bind worden meegegeven, liggen ook vast. De teruggegeven functie wacht op de resterende argumenten.
const presenterSarah = presenter.bind(person, 30);
presenterSarah('Lille'); // 'Sarah, 30 ans, Lille': alleen het laatste argument moet nog worden meegegeven
const add = (a, b) => a + b;
const add5 = add.bind(null, 5); // this wordt niet gebruikt: null volstaat
add5(3); // 8Het is een eenvoudige manier om een generieke functie te specialiseren zonder een wrapper-functie te schrijven.
Pijlfuncties: wanneer bind overbodig wordt
Een pijlfunctie legt de this vast van de plek waar ze is geschreven, en negeert die daarna. Twee gevolgen.
Ten eerste kunnen call, apply en bind haar this niet veranderen:
const arrow = () => this;
arrow.call({ a: 1 }); // this van de module, niet { a: 1 }Ten tweede, en dat is het gangbare gebruik, behoudt een pijlfunctie die binnen een methode of een klasse is gedeclareerd de juiste this zonder bind:
class Compteur {
n = 0;
// Pijlfunctie-eigenschap: this wordt eens en voor altijd vastgelegd
incrementer = () => {
this.n += 1;
return this.n;
};
}
const c = new Compteur();
const inc = c.incrementer;
inc(); // 1: werkt zonder bind
setTimeout(() => c.incrementer(), 1000); // of een pijlfunctie bij de aanroepKeerzijde: een pijlfunctie-eigenschap wordt voor elke instantie opnieuw aangemaakt, terwijl een gewone methode wordt gedeeld via het prototype. Voor een paar objecten maakt dat niets uit, voor duizenden instanties bespaart de methode die één keer in de constructor wordt gebonden (this.incrementer = this.incrementer.bind(this)) geheugen.
this als tweede argument
Verschillende array-methoden accepteren rechtstreeks de waarde van this die in de callback moet worden gebruikt, waardoor een bind overbodig wordt:
const config = { k: 10 };
[1, 2, 3].map(function (x) { return this.k * x; }, config); // [10, 20, 30]forEach, filter, some en every hebben datzelfde tweede argument. Met een pijlfunctie wordt het overbodig.
Vergelijkingstabel
call | apply | bind | |
|---|---|---|---|
| Voert de functie uit | meteen | meteen | nee: geeft een functie terug |
| Argumenten | één voor één | in een array | één voor één, vastgelegd voor latere aanroepen |
| Typisch gebruik | een methode lenen | een array uitspreiden (vervangen door ...) | callbacks, events, partiële toepassing |
| Effect op een pijlfunctie | geen op this | geen op this | geen op this, de argumenten liggen vast |
Veelvoorkomende fouten en hoe je ze leest
- “Cannot read properties of undefined (reading ‘n’)” in een klassemethode: de methode is losgekoppeld van haar instantie (regel 2). Bind haar met
bindof declareer haar als pijlfunctie-eigenschap. - Een event listener die de status niet aanpast:
thisis het DOM-element, niet je object. Zelfde remedie. - Een
binddie niets lijkt te doen: de functie is een pijlfunctie. Verwijder debind, hij is overbodig. removeEventListenerdie niets verwijdert: elkebindmaakt een nieuwe functie aan, je moet dezelfde gebonden referentie meegeven bij het toevoegen en bij het verwijderen.
Voor het vervolg komen klassen in JavaScript terug op methoden en het prototype, en behandelt het hoofdstuk Functies van de tutorial de basis als het vocabulaire van dit artikel nog nieuw is. In PHP ligt de vraag anders: $this verandert nooit van object.
Veelgemaakte fouten
bouton.addEventListener('click', compteur.incrementer) roept de functie aan met this gelijk aan de knop, niet aan compteur. Schrijf compteur.incrementer.bind(compteur) of gebruik een pijlfunctie.this: call, apply en bind veranderen er niets aan. Alleen de argumenten die via bind worden meegegeven, tellen mee.bind maakt bij elke aanroep een nieuwe functie aan. Bind in een loop of een herhaalde render één keer in de constructor (of declareer de methode als pijlfunctie-eigenschap).f.call(obj, [1, 2]) geeft één enkel argument door, de array. Om een array uit te spreiden gebruik je apply, of f.call(obj, ...tableau).

