
PrathamHow to take control of this — and borrow functions like a pro. In a previous article, I covered...
How to take control of this — and borrow functions like a pro.
In a previous article, I covered how this works in JavaScript — it refers to the object that is calling the function. Simple rule: look left of the dot.
But here's the thing that bugged me: what if I want to control what this refers to? What if I have a function that belongs to one object, but I want to use it with a different object? Am I supposed to copy-paste the function? Create a duplicate?
Nope. JavaScript gives you three methods to explicitly set this: call(), apply(), and bind(). They're like a remote control for this — you decide exactly what it points to.
These three methods were one of those topics in the ChaiCode Web Dev Cohort 2026 that seemed intimidating at first but turned out to be beautifully simple. Let me show you.
this Means
Before we dive into call, apply, and bind, let's make sure the foundation is solid.
this refers to the object that is calling the function. Think of it as answering the question: "Who is running this code right now?"
const user = {
name: "Pratham",
greet() {
console.log(`Hi, I'm ${this.name}`);
},
};
user.greet(); // "Hi, I'm Pratham"
// Who called greet()? → user → so this = user
this Inside Regular Functions
When a function is called without an object (standalone), this defaults to the global object (or undefined in strict mode):
function sayName() {
console.log(this.name);
}
sayName(); // undefined — no object is calling it
this Inside Object Methods
When called as a method, this is the object:
const person = {
name: "Pratham",
city: "Delhi",
introduce() {
console.log(`${this.name} from ${this.city}`);
},
};
person.introduce(); // "Pratham from Delhi"
const pratham = {
name: "Pratham",
city: "Delhi",
introduce() {
console.log(`${this.name} from ${this.city}`);
},
};
const arjun = {
name: "Arjun",
city: "Mumbai",
};
// Arjun doesn't have an introduce() method.
// Do I copy-paste the function? NO!
// I can BORROW it using call(), apply(), or bind().
This is exactly the problem these three methods solve. Let's look at each one.
call() — Call a Function with a Specific this
call() lets you invoke a function immediately and explicitly set what this should be. You pass the desired this as the first argument, followed by any function arguments.
functionName.call(thisArg, arg1, arg2, ...);
const pratham = {
name: "Pratham",
city: "Delhi",
introduce() {
console.log(`${this.name} from ${this.city}`);
},
};
const arjun = {
name: "Arjun",
city: "Mumbai",
};
// Borrow pratham's method and use it for arjun
pratham.introduce.call(arjun);
// "Arjun from Mumbai"
What happened? We took pratham.introduce and called it, but told JavaScript: "Don't use pratham as this. Use arjun instead." So inside the function, this.name is "Arjun" and this.city is "Mumbai".
function greet(greeting, punctuation) {
console.log(`${greeting}, I'm ${this.name}${punctuation}`);
}
const user = { name: "Pratham" };
greet.call(user, "Hello", "!!");
// "Hello, I'm Pratham!!"
greet.call({ name: "Arjun" }, "Hey", ".");
// "Hey, I'm Arjun."
Arguments after thisArg are passed to the function one by one, just like a normal function call.
call() Works
greet.call(user, "Hello", "!!")
↓ ↓ ↓
this arg1 arg2
Inside greet():
this = user → this.name = "Pratham"
greeting = "Hello"
punctuation = "!!"
Result: "Hello, I'm Pratham!!"
apply() — Same as call(), But Arguments in an Array
apply() does exactly the same thing as call(). The only difference: instead of passing arguments one by one, you pass them as an array.
functionName.apply(thisArg, [arg1, arg2, ...]);
function greet(greeting, punctuation) {
console.log(`${greeting}, I'm ${this.name}${punctuation}`);
}
const user = { name: "Pratham" };
// call — arguments one by one
greet.call(user, "Hello", "!!");
// "Hello, I'm Pratham!!"
// apply — arguments as an array
greet.apply(user, ["Hello", "!!"]);
// "Hello, I'm Pratham!!"
Same result. Same behavior. The only difference is how you pass the arguments.
apply() Useful?
When your arguments are already in an array:
function introduce(greeting, role) {
console.log(`${greeting}! I'm ${this.name}, a ${role}.`);
}
const user = { name: "Pratham" };
const args = ["Hey", "Full-Stack Developer"];
// apply is natural here — args is already an array
introduce.apply(user, args);
// "Hey! I'm Pratham, a Full-Stack Developer."
A classic example — finding the max of an array:
const numbers = [5, 12, 8, 130, 44];
// Math.max doesn't accept arrays, but apply spreads them as arguments
const max = Math.max.apply(null, numbers);
console.log(max); // 130
💡 Note: With modern JavaScript, you can use
Math.max(...numbers)with the spread operator. Butapply()was the way to do this before spread existed, and understanding it helps in interviews.
bind() — Create a New Function with a Fixed this
Here's where bind() differs from the other two. bind() does NOT call the function immediately. Instead, it returns a new function where this is permanently set to whatever you specify.
const newFunction = functionName.bind(thisArg, arg1, arg2, ...);
// Later:
newFunction(); // this is permanently bound
const pratham = {
name: "Pratham",
greet() {
console.log(`Hi, I'm ${this.name}`);
},
};
const arjun = { name: "Arjun" };
// bind() returns a NEW function — doesn't call it yet
const arjunGreet = pratham.greet.bind(arjun);
// Call it whenever you want
arjunGreet(); // "Hi, I'm Arjun"
arjunGreet(); // "Hi, I'm Arjun" — this is permanently bound
You can also pre-fill some arguments with bind():
function greet(greeting, punctuation) {
console.log(`${greeting}, I'm ${this.name}${punctuation}`);
}
const user = { name: "Pratham" };
// Pre-fill the greeting, leave punctuation for later
const sayHello = greet.bind(user, "Hello");
sayHello("!"); // "Hello, I'm Pratham!"
sayHello("?"); // "Hello, I'm Pratham?"
sayHello("."); // "Hello, I'm Pratham."
bind() locked in this = user and greeting = "Hello". Now sayHello only needs the remaining argument.
Remember the problem where extracting a method loses this?
const user = {
name: "Pratham",
greet() {
console.log(`Hi, I'm ${this.name}`);
},
};
// Extracting the method — context lost!
const greetFn = user.greet;
greetFn(); // "Hi, I'm undefined" ❌
// Fix it with bind()
const greetFn = user.greet.bind(user);
greetFn(); // "Hi, I'm Pratham" ✅
This is especially useful with event handlers and callbacks:
const app = {
name: "FeedPulse",
start() {
console.log(`${this.name} is starting...`);
},
};
// Without bind — this is lost in setTimeout
setTimeout(app.start, 1000);
// "undefined is starting..." ❌
// With bind — this is preserved
setTimeout(app.start.bind(app), 1000);
// "FeedPulse is starting..." ✅
call() vs apply() vs bind() — The Comparison
| Feature | call() |
apply() |
bind() |
|---|---|---|---|
| Calls function? | ✅ Immediately | ✅ Immediately | ❌ Returns a new function |
Sets this? |
✅ Yes | ✅ Yes | ✅ Yes (permanently) |
| Arguments | One by one | As an array | One by one (can pre-fill) |
| Syntax | fn.call(obj, a, b) |
fn.apply(obj, [a, b]) |
fn.bind(obj, a, b) |
| Returns | Function's return value | Function's return value | A new bound function |
| Best for | Borrowing methods, one-time | Array arguments, one-time | Callbacks, event handlers |
call — Comma separated arguments, Calls immediatelyapply — Array of arguments, Also calls immediatelybind — Binds and returns, call it later (Be patient)Here's the complete picture of how this is determined:
┌─────────────────────────────────────────────────────────────────┐
│ WHO IS `this`? │
├─────────────────────┬───────────────────────────────────────────┤
│ │ │
│ obj.method() │ this = obj (left of the dot) │
│ │ │
│ fn() │ this = global (or undefined in strict) │
│ │ │
│ fn.call(obj) │ this = obj (explicitly set) │
│ │ │
│ fn.apply(obj) │ this = obj (explicitly set) │
│ │ │
│ fn.bind(obj)() │ this = obj (permanently bound) │
│ │ │
│ () => {} │ this = surrounding scope (inherited) │
│ │ │
│ new Constructor() │ this = the new object │
│ │ │
└─────────────────────┴───────────────────────────────────────────┘
Priority (highest to lowest):
1. new
2. bind
3. call / apply
4. obj.method()
5. standalone fn()
Use a method from one object on another without duplicating code:
const admin = {
role: "admin",
permissions: ["read", "write", "delete"],
showAccess() {
console.log(`${this.name} (${this.role}): ${this.permissions.join(", ")}`);
},
};
const user = {
name: "Pratham",
role: "editor",
permissions: ["read", "write"],
};
admin.showAccess.call(user);
// "Pratham (editor): read, write"
The classic — using array methods on array-like objects:
function logArgs() {
// 'arguments' is array-like but NOT an array
// Borrow Array's forEach to iterate it
Array.prototype.forEach.call(arguments, (arg) => {
console.log(arg);
});
}
logArgs("JavaScript", "React", "Node.js");
// "JavaScript"
// "React"
// "Node.js"
const counter = {
count: 0,
increment() {
this.count++;
console.log(`Count: ${this.count}`);
},
};
// Without bind — this would be the button element, not counter
// document.querySelector("#btn").addEventListener("click", counter.increment.bind(counter));
bind()
Create specialized functions from general ones:
function multiply(a, b) {
return a * b;
}
const double = multiply.bind(null, 2);
const triple = multiply.bind(null, 3);
console.log(double(5)); // 10
console.log(double(12)); // 24
console.log(triple(5)); // 15
console.log(triple(12)); // 36
We don't care about this here (passed null), but we pre-fill the first argument. Now double always multiplies by 2 and triple always multiplies by 3.
this
const developer = {
name: "Pratham",
stack: "MERN",
describe() {
console.log(`${this.name} is a ${this.stack} developer.`);
},
};
developer.describe(); // "Pratham is a MERN developer."
call()
const designer = {
name: "Priya",
stack: "UI/UX",
};
// Borrow developer's method for designer
developer.describe.call(designer);
// "Priya is a UI/UX developer."
apply() with Array Arguments
function buildProfile(role, experience) {
console.log(`${this.name} — ${role} with ${experience} years of experience.`);
}
const user = { name: "Pratham" };
const args = ["Full-Stack Developer", 1];
buildProfile.apply(user, args);
// "Pratham — Full-Stack Developer with 1 years of experience."
bind() and Store the Function
const boundDescribe = developer.describe.bind({ name: "Arjun", stack: "Python" });
// Call it later — this is permanently set
boundDescribe();
// "Arjun is a Python developer."
// Even passing to setTimeout works
setTimeout(boundDescribe, 1000);
// (after 1 second) "Arjun is a Python developer."
this is determined by who calls the function. Look left of the dot. No dot = global or undefined.call() invokes the function immediately with a specified this and comma-separated arguments.apply() does the same as call(), but takes arguments as an array. Remember: Apply = Array.bind() does NOT call the function. It returns a new function with this permanently locked. Use it for callbacks and event handlers.call(), apply(), and bind() are the tools that give you full control over this. Instead of being at the mercy of JavaScript's calling context rules, you explicitly decide what this should be. Method borrowing, event handler binding, partial application — these patterns come up constantly in real-world code and interviews.
I'm working through all of this in the ChaiCode Web Dev Cohort 2026 under Hitesh Chaudhary and Piyush Garg. Understanding call, apply, and bind was one of those "aha" moments where a bunch of confusing patterns suddenly made sense. If this has been frustrating you, I hope this article cleared things up.
Connect with me on LinkedIn or visit PrathamDEV.in. More articles on the way as the journey continues.
Happy coding! 🚀
Written by Pratham Bhardwaj | Web Dev Cohort 2026, ChaiCode