
call() and apply() run a function immediately with a this of your choosing; call takes the arguments one by one, apply takes them in an array. bind() runs nothing: it returns a new function whose this (and optionally its first arguments) are fixed for good. You use it when a method is passed as a callback and loses its object.
Every JavaScript function has three methods, call(), apply() and bind(), that serve the same purpose: deciding what this is when the function runs. To understand what they are for, you first need to understand the problem they solve: in JavaScript, this does not depend on where a function is written, but on how it is called.
The problem: this changes with the call
Four rules are enough to predict this in almost every case:
- Method call
objet.fonction():thisisobjet. - Plain call
fonction():thisisundefinedin strict mode (modules, classes), and the global objectwindowin non-strict mode. - Call with
new:thisis the object under construction. - Call through
call,applyor a function bound withbind:thisis whatever you decided.
Arrow functions escape these rules: they have no this of their own and keep the one from the surrounding code.
The case that hurts in practice is the slide from rule 1 to rule 2:
const person = {
name: 'Sarah',
hello() {
return `Bonjour ${this.name}`;
},
};
person.hello(); // 'Bonjour Sarah' : method call, this = person
const detached = person.hello;
detached(); // TypeError in strict mode: this is undefined
// ('Bonjour undefined' or 'Bonjour ' in non-strict mode)The function has not changed. Only the way it is called has, and this with it. That is exactly what happens when you pass a method to setTimeout, addEventListener or map.
call(): calling with a this of your choice
fonction.call(thisArg, arg1, arg2, …) runs the function immediately, with this equal to thisArg and the arguments passed one by one.
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; // the method from the previous example
detached.call(person); // 'Bonjour Sarah' : this is restoredA classic use: reusing an array method on something that is not an array.
Array.prototype.slice.call('abc'); // ['a', 'b', 'c']
// Nowadays you would write:
Array.from('abc'); // ['a', 'b', 'c']apply(): the same thing, with the arguments in an array
fonction.apply(thisArg, [arg1, arg2, …]) does exactly what call does, but receives the arguments as an array. Handy when they already come grouped.
presenter.apply(person, [30, 'Lille']); // 'Sarah, 30 ans, Lille'
const notes = [3, 9, 4];
Math.max.apply(null, notes); // 9 : Math.max expects separate arguments, not an arraySince ES2015, the spread operator has made apply rarely necessary: Math.max(...notes) and presenter.call(person, ...args) do the same job while staying readable. You mostly come across apply in code written before then.
bind(): fixing this for later
Where call and apply run straight away, bind runs nothing. It returns a new function whose this is fixed for good. It is the tool for callbacks: you prepare the function now, and someone else will call it later.
const bound = person.hello.bind(person);
bound(); // 'Bonjour Sarah', whatever the calling context
bound.call({ name: 'X' }); // 'Bonjour Sarah' : bind wins, call can no longer change this
bound.name; // 'bound hello'The typical case: a method passed as a callback
class Compteur {
constructor() {
this.n = 0;
}
incrementer() {
this.n += 1;
return this.n;
}
}
const compteur = new Compteur();
// Without bind: the method is detached from its object
const inc = compteur.incrementer;
inc(); // TypeError: Cannot read properties of undefined (reading 'n')
// With bind: this is fixed to compteur
const incLie = compteur.incrementer.bind(compteur);
incLie(); // 1
incLie(); // 2The same pattern applies to timers and browser events:
setTimeout(compteur.incrementer.bind(compteur), 1000);
bouton.addEventListener('click', compteur.incrementer.bind(compteur));
// To be able to remove the listener later, keep the bound reference:
const onClick = compteur.incrementer.bind(compteur);
bouton.addEventListener('click', onClick);
bouton.removeEventListener('click', onClick);Without bind, addEventListener would call incrementer with this set to the clicked button: this.n would be undefined and the counter would count nothing.
bind() for partial application
The arguments passed to bind after this are fixed as well. The returned function expects the remaining ones.
const presenterSarah = presenter.bind(person, 30);
presenterSarah('Lille'); // 'Sarah, 30 ans, Lille' : only the last argument is left to supply
const add = (a, b) => a + b;
const add5 = add.bind(null, 5); // this is not used: null will do
add5(3); // 8It is a simple way to specialise a generic function without writing a wrapper function.
Arrow functions: when bind becomes unnecessary
An arrow function captures the this of the place where it is written and ignores it from then on. Two consequences.
First, call, apply and bind cannot change its this:
const arrow = () => this;
arrow.call({ a: 1 }); // the module's this, not { a: 1 }Second, and this is the everyday use, an arrow function declared inside a method or a class keeps the right this without bind:
class Compteur {
n = 0;
// Arrow-function property: this is captured once and for all
incrementer = () => {
this.n += 1;
return this.n;
};
}
const c = new Compteur();
const inc = c.incrementer;
inc(); // 1 : works without bind
setTimeout(() => c.incrementer(), 1000); // or an arrow function at the call siteThe flip side: an arrow-function property is recreated for every instance, whereas a regular method is shared through the prototype. For a handful of objects it makes no difference; for thousands of instances, a method bound once in the constructor (this.incrementer = this.incrementer.bind(this)) saves memory.
this passed as a second argument
Several array methods accept the this value to use in the callback directly, which saves a bind:
const config = { k: 10 };
[1, 2, 3].map(function (x) { return this.k * x; }, config); // [10, 20, 30]forEach, filter, some and every take the same second argument. With an arrow function, it becomes unnecessary.
Comparison table
call | apply | bind | |
|---|---|---|---|
| Runs the function | immediately | immediately | no: returns a function |
| Arguments | one by one | in an array | one by one, fixed for future calls |
| Typical use | borrowing a method | spreading an array (replaced by ...) | callbacks, events, partial application |
| Effect on an arrow function | none on this | none on this | none on this, the arguments are fixed |
Common errors and how to read them
- “Cannot read properties of undefined (reading ‘n’)” in a class method: the method has been detached from its instance (rule 2). Bind it with
bindor declare it as an arrow-function property. - An event listener that does not update the state:
thisis the DOM element, not your object. Same remedy. - A
bindthat seems to do nothing: the function is an arrow function. Remove thebind, it is pointless. removeEventListenerthat removes nothing: everybindcreates a new function; you have to pass the same bound reference when adding and when removing.
To go further, classes in JavaScript come back to methods and the prototype, and the Functions chapter of the tutorial lays the groundwork if the vocabulary in this article is still new. In PHP, the question arises differently: $this never changes object.
Common errors
bouton.addEventListener('click', compteur.incrementer) calls the function with this set to the button, not to compteur. Write compteur.incrementer.bind(compteur) or use an arrow function.this of its own: call, apply and bind leave it untouched. Only the arguments passed through bind are taken into account.bind creates a new function on every call. In a loop or a repeated render, bind once in the constructor (or declare the method as an arrow-function property).f.call(obj, [1, 2]) passes a single argument, the array. To spread an array, use apply, or f.call(obj, ...tableau).

