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

Search for a command to run...

No comments yet. Be the first to comment.
Introduction If you've ever built something with vanilla JavaScript and had to update the UI frequently, you already know the pain. You call document.getElementById, change some text, update a class,
Introduction If you've ever installed and tried out Linux after seeing a cool hacking sequence in a sci-fi movie, you know that opening it feels like stepping into a strange city with many roads named

Introduction Most JavaScript confusion around this comes from one wrong assumption: that this depends on where a function is defined. It doesn't. It depends on who called the function. That one shift

Introduction JavaScript gives you objects and arrays out of the box, and for a long time those two were the default answer to almost every data storage problem. Objects for key-value pairs, arrays for

Introduction Most JavaScript codebases are full of code like this: const user = { name: "Sara", age: 28, city: "Mumbai" }; const name = user.name; const age = user.age; const city = user.city; Three

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.
this Actually MeansThe 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 Functionfunction 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 ObjectThings 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 thiscall() 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 Arrayapply() 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 Functioncall() 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.
call vs apply vs bindcall() |
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 |
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.
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.
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.