The Magic of this, call(), apply(), and bind() in JavaScript

Introduction
If you've spent any time with JavaScript, you've probably run into this and walked away confused. It behaves differently depending on where it shows up. And call(), apply(), and bind() sound similar enough that most people lump them together without really understanding what each one does differently.
This post clears all of that up. No execution context internals, no spec language. Just what you need to know to write better code.
What this Actually Means
The simplest way to think about this: it refers to whoever called the function.
Not where the function was defined. Not where it lives in your code. Who called it.
That one shift in thinking makes most of the confusion go away.
this Inside a Regular Function
function greet() {
console.log(this);
}
greet();
When you call a plain function like this, this points to the global object. In a browser, that's window. In Node.js, it's global. In strict mode, it's undefined.
Nobody specific called it, so JavaScript falls back to the global object by default.
this Inside an Object
Things get more useful when functions live inside objects.
const person = {
name: "Alice",
greet: function () {
console.log("Hi, I'm " + this.name);
}
};
person.greet(); // Hi, I'm Alice
Here, person is the one calling greet. So this is person, and this.name is "Alice".
Now watch what happens when you pull the function out of the object:
const greet = person.greet;
greet(); // Hi, I'm undefined
The function is the same. But now nobody owns the call. this falls back to the global object, and name isn't defined there, so you get undefined.
This is the source of most this bugs.
call(): Borrow a Function, Choose Your this
call() lets you run a function and tell it exactly what this should be.
const person1 = { name: "Alice" };
const person2 = { name: "Bob" };
function greet(city) {
console.log(this.name + " lives in " + city);
}
greet.call(person1, "New York"); // Alice lives in New York
greet.call(person2, "London"); // Bob lives in London
The first argument to call() sets this. Everything after that gets passed as individual arguments to the function.
Think of it as: "Run this function, but pretend it belongs to this object."
apply(): Same Idea, Arguments as an Array
apply() works exactly like call(). The only difference is how you pass arguments. Instead of listing them one by one, you pass an array.
function greet(city, country) {
console.log(this.name + " is from " + city + ", " + country);
}
const person = { name: "Alice" };
greet.apply(person, ["Paris", "France"]); // Alice is from Paris, France
Both lines below do the same thing:
greet.call(person, "Paris", "France");
greet.apply(person, ["Paris", "France"]);
apply() is handy when your arguments are already stored in an array.
bind(): Create a Fixed Version of a Function
call() and apply() run the function immediately. bind() is different. It returns a new function with this permanently locked in, but doesn't call it yet.
const person = { name: "Alice" };
function greet() {
console.log("Hi, I'm " + this.name);
}
const greetAlice = greet.bind(person);
greetAlice(); // Hi, I'm Alice
greetAlice(); // Hi, I'm Alice (always Alice, no matter what)
You can store that bound function and call it later. It will always use the this you gave it.
This is especially useful for event handlers and callbacks, where you lose control of what this will be.
const button = {
label: "Submit",
handleClick: function () {
console.log("Clicked: " + this.label);
}
};
const handler = button.handleClick.bind(button);
// Later, passed as a callback
setTimeout(handler, 1000); // Clicked: Submit
Without bind(), this inside setTimeout would not be button.
Comparison: call vs apply vs bind
call() |
apply() |
bind() |
|
|---|---|---|---|
| Runs the function? | Yes, immediately | Yes, immediately | No, returns a new function |
| How you pass args | One by one | As an array | One by one (at bind time or call time) |
Fixes this? |
For that call only | For that call only | Permanently |
| Returns | Function result | Function result | New function |
When to Use Each One
Use call() when you want to run a function once with a specific this and you have the arguments ready individually.
Use apply() when you have the same situation but your arguments are already in an array.
Use bind() when you need to pass a function somewhere (a callback, an event listener, a timeout) and you want to make sure this stays what you intend it to be.
One More Thing Worth Knowing
Arrow functions ignore all of this. They don't have their own this. They inherit it from the surrounding scope and call(), apply(), and bind() cannot change that.
const person = {
name: "Alice",
greet: () => {
console.log(this.name); // undefined, always
}
};
person.greet();
Arrow functions are great in many situations, but if you need this to refer to the object, use a regular function.
Conclusion
Once you stop thinking of this as something mysterious and start thinking of it as "who called this function," the rest follows. call() and apply() let you pick the caller. bind() locks one in for good.




