The Magic of this, call(), apply(), and bind() in JavaScript
this is one of those JavaScript concepts that looks simple on the surface but surprises you in practice. And once you understand it, call(), apply(), and bind() start to make perfect sense because they're really just tools for controlling what this points to.
Let's take it one step at a time.
What Is this?
this is a special keyword in JavaScript that refers to whoever is calling the function right now. It's not fixed , it changes depending on how and where a function is invoked.
A simple way to think about it:
this= the object in front of the dot when you call a method.
const person = {
name: "Alice",
greet() {
console.log("Hi, I'm " + this.name);
}
};
person.greet(); // "Hi, I'm Alice"
When you call person.greet(), the object in front of the dot is person. So inside greet, this is person, and this.name is "Alice".
this Inside a Normal Function
When a regular function is called on its own not attached to any object , this behaves differently.
In strict mode (which modern JavaScript uses in modules and classes), this is undefined:
function sayHello() {
console.log(this); // undefined (in strict mode)
}
sayHello();
In non-strict mode (e.g., a plain script), this defaults to the global object (window in browsers):
function sayHello() {
console.log(this); // Window { ... } in a browser
}
sayHello();
The key takeaway: a standalone function call has no owner, so this is either undefined or the global object is not something useful.
this Inside an Object
When a function is called as a method of an object, this refers to that object:
const car = {
brand: "Toyota",
model: "Camry",
describe() {
return this.brand + " " + this.model;
}
};
console.log(car.describe()); // "Toyota Camry"
this.brand works because car is the one calling describe().
Now look what happens when you take the method out of the object:
const car = {
brand: "Toyota",
describe() {
return this.brand;
}
};
const fn = car.describe; // copy the function — no object attached
console.log(fn()); // undefined ← this is no longer car!
The function is the same, but calling it without an object means this no longer points to car. This is exactly the problem that call(), apply(), and bind() solve.
Who's Calling? A Visual
person.greet()
│ │
│ |── the function being called
|───────── this = person (the caller)
greet()
│
|── no object in front → this = undefined or global
call() — Borrow a Function, Pass Arguments One by One
call() lets you invoke a function and manually set what this should be. You pass the target object as the first argument, then any function arguments after it.
const person1 = { name: "Alice", age: 25 };
const person2 = { name: "Bob", age: 30 };
function introduce(city, country) {
return this.name + " (" + this.age + ") from " + city + ", " + country;
}
console.log(introduce.call(person1, "Mumbai", "India"));
// "Alice (25) from Mumbai, India"
console.log(introduce.call(person2, "London", "UK"));
// "Bob (30) from London, UK"
You're borrowing introduce and telling it: "Run this function, but treat person1 as this."
The structure:
functionName.call(thisArg, arg1, arg2, ...)
apply() — Same as call(), But Arguments as an Array
apply() works exactly like call() you set this manually but instead of passing arguments one by one, you pass them as a single array.
const person1 = { name: "Alice", age: 25 };
function introduce(city, country) {
return this.name + " (" + this.age + ") from " + city + ", " + country;
}
const args = ["Mumbai", "India"];
console.log(introduce.apply(person1, args));
// "Alice (25) from Mumbai, India"
The result is identical to call(). The only difference is how you pass the arguments.
The structure:
functionName.apply(thisArg, [arg1, arg2, ...])
A handy use case — apply() with Math.max:
const scores = [88, 92, 76, 95, 84];
console.log(Math.max.apply(null, scores)); // 95
// null because Math.max doesn't use "this"
// apply spreads the array as individual arguments
bind() — Create a New Function with this Locked In
Unlike call() and apply() which immediately invoke the function, bind() returns a new function with this permanently set. You can store it and call it later.
const person = { name: "Alice", age: 25 };
function introduce(city) {
return this.name + " from " + city;
}
const aliceIntroduce = introduce.bind(person);
// aliceIntroduce is a new function — "this" is always person
console.log(aliceIntroduce("Mumbai")); // "Alice from Mumbai"
console.log(aliceIntroduce("Delhi")); // "Alice from Delhi"
No matter how or where you call aliceIntroduce, this will always be person.
This is especially useful when passing methods as callbacks:
const timer = {
message: "Time's up!",
start() {
setTimeout(this.alert.bind(this), 1000); // bind this so it doesn't get lost
},
alert() {
console.log(this.message);
}
};
timer.start(); // After 1 second: "Time's up!"
Without bind, this inside alert would no longer refer to timer when setTimeout calls it.
The structure:
const newFn = functionName.bind(thisArg, arg1, arg2, ...)
call vs apply vs bind — The Full Picture
call() |
apply() |
bind() |
|
|---|---|---|---|
| Invokes immediately? | ✅ Yes | ✅ Yes | ❌ No — returns new function |
| How arguments are passed | One by one: fn.call(obj, a, b) |
As array: fn.apply(obj, [a, b]) |
One by one: fn.bind(obj, a, b) |
| Returns | Result of function | Result of function | A new bound function |
| When to use | Call once, control this |
Call once, args already in array | Store for later, lock this permanently |
All three do the same core thing — let you control what this is. The difference is when the function runs and how arguments are passed.
Side-by-Side Code Comparison
const user = { name: "Riya" };
function greet(greeting, punctuation) {
return greeting + ", " + this.name + punctuation;
}
// call() — invoke now, args one by one
greet.call(user, "Hello", "!");
// "Hello, Riya!"
// apply() — invoke now, args as array
greet.apply(user, ["Hello", "!"]);
// "Hello, Riya!"
// bind() — don't invoke yet, return new function
const greetRiya = greet.bind(user, "Hello");
greetRiya("!"); // "Hello, Riya!"
greetRiya("?"); // "Hello, Riya?" ← can be called multiple times
Quick Reference
// this inside an object method
const obj = {
name: "Alice",
sayName() { console.log(this.name); } // this = obj
};
// call — invoke immediately, pass args one by one
fn.call(thisArg, arg1, arg2);
// apply — invoke immediately, pass args as array
fn.apply(thisArg, [arg1, arg2]);
// bind — return new function with this locked in
const boundFn = fn.bind(thisArg, arg1);
boundFn(arg2); // call later
Wrapping Up
this is context-dependent — it changes based on who calls the function. And call(), apply(), bind() are simply three ways to take control of that context yourself.
Here's what to carry forward:
thisrefers to the object that called the function — the object in front of the dotA standalone function call has no owner, so
thisbecomesundefinedor globalcall()invokes a function immediately with a customthis, arguments passed one by oneapply()does the same but accepts arguments as an arraybind()does not call the function — it returns a new function withthispermanently setUse
bind()when you need to pass a method as a callback and don't want to lose thethiscontext
Open your console, create a couple of objects, and try borrowing a method between them using all three. Once you see the same function behave differently based on what this is — the concept will fully click. 🚀