Understanding the Differences Between call, bind, and apply in JavaScript
A Simple Guide to Bind, Call, and Apply
Overview
Back when I was working with class-based components in React Native, I always wrote something like this.function.bind(this)
but never really thought about why — I just memorized that I had to do it. For example:
constructor(props) {
super(props)
this.state = {
...
}
this.onSave = this.onSave.bind(this)
}
It wasn’t until a technical interview where I was given some code and quizzed on the use of bind that I first learned about call and apply. This experience helped me understand these important JavaScript methods more deeply.
Now, what are they?
Bind
A method that creates a new function with a specific this context permanently set. It does not call the function immediately but returns a new version of the function that you can invoke later, ensuring this always refers to the object you want.
Call
A method that immediately invokes a function, allowing you to specify what this should be inside that function. You pass in the this context followed by the function’s arguments one by one.
Apply
A method similar to call in that it immediately invokes the function and sets the this context. The key difference is that the function’s arguments must be passed as an array (or array-like object) instead of individually.
A Practical Example
Let’s look at an example involving Pokémon to see how these methods work in practice:
const whatsThatPokemon = {
name: "Squirtle",
action: function (battleCry) {
console.log(`${battleCry} ${this.name}!`);
},
};
whatsThatPokemon.action("GO GO GO"); // Output: GO GO GO Squirtle
// What if we store the action method in a variable?
const pokName = whatsThatPokemon.action;
pokName("GO GO GO");
If you declare a const, assign the action method to it, and attempt to invoke the method, then you will get GO GO GO undefined
, but why? The problem is that when the method is assigned to a variable and called, the default this refers to the global object (or undefined in strict mode), which doesn’t have a name property. Thus, we need to define what the this object looks like. For example:
const boundCharmander = whatsThatPokemon.action.bind({ name: "Charmander" });
boundCharmander("LET'S GO");
Then we get the following output: LET'S GO Charmander!
Alternatively, we can also do this to get the same output. bind can also preset arguments (called “partial application”), which is useful in some contexts.:
const presetCharmander = whatsThatPokemon.action.bind({ name: "Charmander" }, "LET'S GO");
presetCharmander();
Next, let’s say we want to call the function without having to assign it to a variable, then we can use call like this to get RAAAH Bulbasaur!
:
whatsThatPokemon.action.call({ name: "Bulbasaur" }, "RAAAH");
We can use apply in a similar fashion, but then instead, pass in the arguments as part of an array
whatsThatPokemon.action.apply({ name: "Bulbasaur" }, ["RAAAH"]);
This and Scope
If you’re using arrow functions, you might notice you don’t need to worry about .bind(this)
at all. That’s because arrow functions don’t have their own this context. They inherit it from the scope in which they were defined.
this.name = "Brock";
const trainer = {
name: "Ash",
speak: function () {
console.log(`Hello, I'm ${this.name}`);
},
shout: () => {
console.log(`HELLO, I'M ${this.name}`);
},
};
trainer.speak(); // Output: Hello, I'm Ash
trainer.shout(); // Output: HELLO, I'M Brock
What’s happening here?
trainer.speak() is a regular function. When called with trainer.speak(), the value of this is set to trainer, so this.name equals "Ash".
trainer.shout() is an arrow function. Arrow functions don’t get their own this; instead, they inherit this from the scope where they were created — in this case, the global scope, where we previously set
this.name = "Brock"
.
Even though shout is a property of trainer, it doesn’t matter because arrow functions don’t dynamically bind this based on how they’re called — they lexically capture the value of this at the time of definition.
Conclusion
At first glance, bind, call, and apply might seem interchangeable because they all let you control what this refers to, but knowing their subtle differences. When they invoke a function, how they pass arguments, and whether they create a new function or call it immediately, is what separates memorizing from truly understanding.
If you’ve ever mindlessly chained .bind(this)
in a class-based React or React Native component, you’re not alone. Hopefully now, next time you see (or write) these methods, you’ll know exactly what they’re doing, when to use each one, and why they matter.