The Magic of this, call(), apply(), and bind() in JavaScript
Every time he steps on a new stage, he forgets who he is. His agent has three tools to remind him.

Turning chai into code and ideas into full-stack applications. Sharing lessons from my development journey, one commit at a time.
The Actor with Amnesia β this, call, apply, bind
Every time he steps on a new stage, he forgets who he is. His agent has three tools to remind him.
Before you read this, you should know:
What a JavaScript function is (
function greet() {})What an object is (
{ name: "Aryan", age: 22 })How to call a function (
greet())
After reading this, you will understand:
What
thisis and why JavaScript has itWhy
thisgives different answers in different situationsThe "left of the dot" rule β the one rule that explains everything
What
call()does, step by step, with storyWhat
apply()does and when arrays make it cleanerWhat
bind()does and why callbacks need itArrow functions and
thisβ the one big exception5 common mistakes every beginner makes β shown and fixed
A full mini-theatre simulation you build line by line
π¬ The Story Begins
It's opening night. The Grand Vikram Theatre in Mumbai is packed β 800 seats, all taken.
Rohan is one of India's greatest method actors. He doesn't just play a character. He becomes one. This week he's been eating, sleeping, and thinking as Raghav β the cold, calculating villain of Act III.
He stands in the wings. The curtain rises. He steps through the stage door β
And walks onto the wrong stage. This is the comedy theatre next door. Different lights. Different set. Different crew looking at him in confusion.
Rohan freezes. Looks around. Blinks.
"Who⦠am I?"
That is this in JavaScript.
A function is like Rohan β talented, capable, ready to perform. But it doesn't always know who it belongs to in that moment. The answer depends entirely on which stage it's standing on when it's called.
Rohan's agent β a calm professional named Dev β watches from the side. He opens his briefcase and pulls out three laminated cards:
π΅
call()β "You are Rohan. This stage is yours. Go. NOW."π’
apply()β "Here's your script in one envelope. Go. NOW."π£
bind()β "Here is your identity badge. Keep it forever. Use it whenever the curtain rises."
Those three tools are what this entire blog is about.
But first β we need to understand why Rohan forgets in the first place.
π Act One β What is this?
The simplest possible explanation
this is a keyword that means: "whoever is calling this function right now."
That's it. Really.
When a function runs, JavaScript automatically sets a variable called this inside it. The value of this is determined by who called the function β not where the function was written, not what file it lives in. Just who called it, at the moment it ran.
Think of it like a stage board that shows the actor's name. Every time Rohan steps on a new stage, the board shows a different name. Rohan reads the board. That's this.
Your very first this β see it live
Open your browser console right now. Type this:
// The simplest example possible
const actor = {
name: "Rohan",
greet: function() {
console.log("Hello, I am " + this.name);
}
};
actor.greet();
// Output: "Hello, I am Rohan"
Here is what happened, step by step:
We created an object called
actorwith anameand agreetfunctionInside
greet, we wrotethis.nameWe called
actor.greet()JavaScript said: "The object calling this function is
actor. Sothis = actor. Sothis.name = actor.name = "Rohan"."
That's the whole idea. this = the object that called the function.
Now watch this change when the caller changes
// Same greet function β shared between two different objects
function greet() {
console.log("Hello, I am " + this.name);
}
const actor = { name: "Rohan", greet: greet };
const director = { name: "Meera", greet: greet };
const producer = { name: "Vikram", greet: greet };
actor.greet(); // "Hello, I am Rohan"
director.greet(); // "Hello, I am Meera"
producer.greet(); // "Hello, I am Vikram"
The function greet is identical every time. It didn't change. But this.name gave three different answers because three different objects did the calling.
Rohan stepped on three different stages. Each stage had a different name on the board. He read the board each time.
π The Golden Rule of this
You need exactly one rule to understand 90% of this behaviour:
this= the object to the LEFT of the dot when the function is called.
actor.greet();
//β
// actor is left of the dot β this = actor
director.greet();
// β
// director is left of the dot β this = director
greet();
// no dot, no object β this = undefined
That last line is important. When there is no dot β no object calling the function directly β this becomes undefined (in strict mode) or the global window object (in browsers without strict mode). Neither is useful. Both are bugs.
Proving the rule yourself
const theatre = {
name: "Grand Vikram Theatre",
describe: function() {
console.log("This is: " + this.name);
}
};
const homeStage = {
name: "Rohan's Living Room",
describe: theatre.describe // same function, different object
};
theatre.describe(); // "This is: Grand Vikram Theatre"
homeStage.describe(); // "This is: Rohan's Living Room"
// Pull the function out with no object
const bare = theatre.describe;
bare(); // "This is: undefined" β no dot, no owner
That last example β storing the function in bare and calling bare() β is called losing context. The dot connection is gone. this has no answer.
This is the root cause of almost every this bug a beginner encounters.
ποΈ this in Every Situation β The Complete Map
Before meeting the agent's tools, let's walk through every place a beginner encounters this. Each one explained with the stage metaphor.
Situation 1 β Method on an object Works
const stage = {
name: "Grand Vikram",
capacity: 800,
announce: function() {
console.log(this.name + " holds " + this.capacity + " seats.");
}
};
stage.announce();
// "Grand Vikram holds 800 seats."
Why it works: stage is left of the dot. this = stage. The stage board shows the right name.
Situation 2 β Standalone function Broken
function announce() {
console.log("Stage: " + this.name);
}
announce();
// "Stage: undefined"
Why it breaks: No dot. No object. this has no value. The stage board is blank.
Situation 3 β Method pulled out into a variable Broken
const stage = {
name: "Grand Vikram",
announce: function() {
console.log(this.name);
}
};
const fn = stage.announce; // just copying the function, not the connection
fn();
// undefined
Why it breaks: You copied the function. You did not copy the connection to stage. When fn() runs, there is no dot, no owner. The badge got left behind when Rohan walked off stage.
Situation 4 β Inside a class Works (same rule)
class Actor {
constructor(name) {
this.name = name; // this = the new instance
}
perform() {
console.log(this.name + " is on stage!"); // this = whoever calls this
}
}
const rohan = new Actor("Rohan");
rohan.perform();
// "Rohan is on stage!"
// this = rohan because rohan is left of the dot
Same golden rule β rohan.perform() β rohan is left of the dot β this = rohan.
Situation 5 β Inside a callback The one that makes beginners cry
const actor = {
name: "Rohan",
enterStage: function() {
console.log(this.name + " enters the stage!");
}
};
// Works fine when YOU call it
actor.enterStage(); // "Rohan enters the stage!" β
// β Breaks inside setTimeout β "this" is lost
setTimeout(actor.enterStage, 1000);
// After 1 second: "undefined enters the stage!"
Why it breaks: You're passing the function itself to setTimeout. setTimeout then calls it later as a plain function β no dot, no stage attached. The badge stayed at the dressing room. Rohan walked on stage without it.
This is exactly what call(), apply(), and bind() are designed to fix.
π΅ call(): The Agent Grabs His Shoulder
Dev watches Rohan standing frozen under the wrong spotlight.
He walks over, grabs him firmly by the shoulder and says:
"Listen to me. Your name is Rohan. THIS is your object. I'm telling you directly. NOW perform."
And Rohan performs.
That's call().
call() lets you run any function immediately, while directly specifying what this should be β even if that function has no natural connection to that object.
The syntax, every part labelled
functionName.call( thisValue, arg1, arg2, arg3 )
// β β β β β
// function what THIS first second third
// to run will be arg arg arg
functionNameβ the function you want to borrow and run.call(β you're invoking thecallmethod that every function hasthisValueβ the object you wantthisto point to inside the functionarg1, arg2, ...β the normal arguments the function needs, separated by commas
Your first call() β zero to working
// Step 1 β A standalone function that uses "this"
function introduce() {
console.log("My name is " + this.name);
}
// Step 2 β Two objects (neither has introduce() as a method)
const rohan = { name: "Rohan" };
const priyanka = { name: "Priyanka" };
// Step 3 β call() tells introduce() who "this" should be
introduce.call(rohan);
// "My name is Rohan"
// this = rohan because we told it so
introduce.call(priyanka);
// "My name is Priyanka"
// same function β different "this"
introduce normally has no idea who rohan is. call() makes the introduction β just for that one performance.
call() with arguments
When your function also needs extra arguments, pass them after the this value, one by one:
function perform(role, emotion) {
console.log(this.name + " plays the " + role + " feeling " + emotion);
}
const rohan = { name: "Rohan" };
const priyanka = { name: "Priyanka" };
// this arg1 arg2
perform.call(rohan, "villain", "furious");
// "Rohan plays the villain feeling furious"
// this arg1 arg2
perform.call(priyanka, "hero", "brave");
// "Priyanka plays the hero feeling brave"
First argument = this value. Everything after = the function's own parameters. In order.
The killer use case β method borrowing
One object has a useful method. Another doesn't β but needs to use it. You don't want to copy the function. You just want to borrow it:
const hero1 = {
name: "Aragorn",
level: 10,
introduce: function() {
return this.name + " is a level " + this.level + " warrior.";
}
};
const hero2 = {
name: "Lyra",
level: 7
// no introduce() method β but we can borrow hero1's
};
const result = hero1.introduce.call(hero2);
console.log(result);
// "Lyra is a level 7 warrior."
// this.name = "Lyra", this.level = 7 β because this = hero2
The function ran exactly as always β but with this pointing to hero2. One function. Two objects. No copy-paste.
Real story moment β Dev's reusable scripts
Dev keeps a collection of performance scripts any actor on his roster can borrow. call() is how he lends them:
// Dev's script library β no actor hardcoded
function deliverLine(line) {
console.log(this.name + " (as " + this.role + "): \"" + line + "\"");
}
function takeACurtainCall() {
console.log(this.name + " bows. The crowd applauds!");
}
// Any actor can borrow any script
const rohan = { name: "Rohan", role: "Raghav the Villain" };
const priyanka = { name: "Priyanka", role: "Aarti the Hero" };
const ensemble = { name: "The Cast", role: "Everyone" };
deliverLine.call(rohan, "You cannot stop what is coming.");
// "Rohan (as Raghav the Villain): "You cannot stop what is coming.""
deliverLine.call(priyanka, "Watch me try.");
// "Priyanka (as Aarti the Hero): "Watch me try.""
takeACurtainCall.call(ensemble);
// "The Cast bows. The crowd applauds!"
One script library. Any actor. call() makes it possible.
π‘
call()in one sentence: Run this function RIGHT NOW, withthis= this specific object, passing arguments one by one after it.
π’ apply(): The Script Arrives in One Envelope
Act II. Opening night. Dev has all of Rohan's notes ready β but tonight, instead of handing them one by one, he puts everything in a single sealed envelope.
"Here is your envelope, Rohan. Your role, your emotion, your opening line β all inside, in order. NOW go perform."
That's apply().
apply() is exactly the same as call() in every way β same immediate execution, same this control, same result. The only difference: instead of listing arguments one by one after the this value, you bundle them all into one array.
The syntax
functionName.apply( thisValue, [arg1, arg2, arg3] )
// β β
// what THIS ALL arguments
// will be wrapped in ONE array
call() vs apply() β the only difference
function perform(role, emotion) {
console.log(this.name + " plays " + role + " feeling " + emotion);
}
const rohan = { name: "Rohan" };
// call() β arguments passed one by one, separated by commas
perform.call(rohan, "villain", "furious");
// apply() β arguments passed as a single array
perform.apply(rohan, ["villain", "furious"]);
// Both produce EXACTLY the same output:
// "Rohan plays villain feeling furious"
That's the entire difference. call() = comma-separated. apply() = array. Same result either way.
When apply() is the better choice
If they produce the same result, why does apply() exist?
Because sometimes your arguments are already stored in an array β and apply() lets you pass that array directly without unpacking it first:
function audition(role, emotion, line) {
console.log(this.name + " auditions as " + role);
console.log("Emotion: " + emotion);
console.log("Opening line: " + line);
}
const rohan = { name: "Rohan" };
// The audition details come pre-packaged (from a database, a list, etc.)
const auditionPackage = ["Villain", "furious", "You cannot stop me."];
// With call() β you must unpack the array manually
audition.call(rohan, auditionPackage[0], auditionPackage[1], auditionPackage[2]);
// messy, error-prone, doesn't scale if there are 8 arguments
// With apply() β pass the array as-is, it spreads automatically
audition.apply(rohan, auditionPackage);
// clean, scalable, no manual unpacking
Dev hands Rohan a sealed envelope. Not twenty loose sheets. One package. That's apply().
The classic apply() trick with Math.max
Before modern JavaScript introduced the spread operator, apply() was used to pass arrays into functions like Math.max that don't normally accept arrays:
const scores = [88, 64, 99, 72, 95];
// Math.max needs individual numbers, not an array
Math.max(88, 64, 99, 72, 95); // 99 β works but must list every number
// apply() spreads the array into individual arguments
Math.max.apply(null, scores); // 99 β same result, array stays intact
// "null" as thisValue = "Math.max doesn't use this, just spread the args"
// Modern ES6 way β spread operator (preferred today)
Math.max(...scores); // 99 β cleanest
π‘
apply()in one sentence: Exactly likecall()β run the function right now with a specificthisβ but pack all the arguments into one array instead of listing them separately.
π£ bind(): The Permanent Identity Badge
It's now Act III. Rohan will be performing across five cities over the next two months β Mumbai, Delhi, Bengaluru, Kolkata, Chennai. Different theatres. Different stages. Different nights. Dev won't always be there.
So this time, Dev does something different.
He pulls out a laminated badge, clips it permanently to Rohan's coat lapel, and says:
"This badge says who you are. Wherever you go, whatever stage you step on, whenever the curtain rises β you are Rohan, playing Raghav. No one can change that. The badge is yours. Forever."
That's bind().
bind() does not call the function. Instead, it creates and returns a brand new function with this permanently wired to the object you specify. You hold onto that new function and call it whenever you're ready.
The syntax
const newFunction = functionName.bind( thisValue )
// β β
// Store the new "this" inside newFunction
// function here will ALWAYS be thisValue
//
// β οΈ bind() RETURNS a function β it does NOT run it
call() vs bind() β the difference in timing
function introduce() {
console.log("I am " + this.name);
}
const rohan = { name: "Rohan" };
// call() β runs IMMEDIATELY right now
introduce.call(rohan);
// Prints: "I am Rohan" β happens instantly
// bind() β does NOT run now. Returns a new locked function.
const rohanIntroduce = introduce.bind(rohan);
// Nothing printed. rohanIntroduce is now a new function.
// YOU decide when to call the bound function
rohanIntroduce();
// Prints: "I am Rohan" β you chose when
rohanIntroduce();
// Prints: "I am Rohan" β still Rohan. Always Rohan.
rohanIntroduce();
// Prints: "I am Rohan" β the badge never falls off
call() = do it now, once. bind() = here's a version that will always do it as this person, use it whenever.
Why bind() exists β fixing the callback problem
This is the most important real-world use of bind(). Let's see the exact problem and the fix:
const actor = {
name: "Rohan",
enterStage: function() {
console.log(this.name + " enters the stage!");
}
};
// Works fine β called directly with the dot
actor.enterStage();
// "Rohan enters the stage!"
// BROKEN β setTimeout calls it as a plain function
setTimeout(actor.enterStage, 2000);
// After 2 seconds: "undefined enters the stage!"
// "this" is lost. The badge stayed at the dressing room.
// FIXED β bind() locks "this" to actor permanently
setTimeout(actor.enterStage.bind(actor), 2000);
// After 2 seconds: "Rohan enters the stage!"
// The badge is clipped on. Rohan knows who he is.
What .bind(actor) does here: It creates a new function. Inside that new function, this is permanently actor no matter what β no matter that setTimeout is calling it, no matter what time it is, no matter where in the code it runs.
Rohan walks on stage in Bengaluru, two months from now, at 9 PM. He looks down. The badge says his name. He knows exactly who he is.
bind() for repeated reuse
Once you have a bound function, you can call it as many times as you like:
const rohan = {
name: "Rohan",
role: "Raghav"
};
function announce() {
return this.name + " is playing " + this.role;
}
// Create it once
const announceRohan = announce.bind(rohan);
// Use it as many times as you need β "this" never changes
console.log(announceRohan()); // "Rohan is playing Raghav"
console.log(announceRohan()); // "Rohan is playing Raghav"
console.log(announceRohan()); // "Rohan is playing Raghav"
bind() with preset arguments β partial application
bind() can do something call() and apply() cannot β it can lock in some arguments upfront so you only pass the remaining ones later:
function perform(role, emotion) {
console.log(this.name + " plays " + role + " feeling " + emotion);
}
const rohan = { name: "Rohan" };
// Rohan is always the villain this season β preset role = "villain"
const rohanAsVillain = perform.bind(rohan, "villain");
// β "role" is now preset
// Now only pass the remaining argument β emotion changes every scene
rohanAsVillain("fury"); // "Rohan plays villain feeling fury"
rohanAsVillain("coldness"); // "Rohan plays villain feeling coldness"
rohanAsVillain("despair"); // "Rohan plays villain feeling despair"
The badge says "Rohan β Villain." The emotion is different every night. bind() locks the first part in so you only need to fill in what changes.
π‘
bind()in one sentence: Returns a NEW function withthis(and optionally some arguments) permanently locked in β does NOT call the function now.
π The Three Tools β Side by Side
Here is the complete comparison. Study this until it's second nature.
call() |
apply() |
bind() |
|
|---|---|---|---|
| Runs the function immediately? | Yes | Yes | No |
| What it returns | Result of function | Result of function | A new bound function |
| How arguments are passed | One by one: a, b, c |
As an array: [a, b, c] |
One by one, optional preset |
| Best for | Borrowing a function once | Borrowing when args are in an array | Callbacks, events, repeated use |
| Syntax | fn.call(obj, a, b) |
fn.apply(obj, [a, b]) |
const f = fn.bind(obj) |
The one memory trick
call β C is for Comma β arguments by Commas
apply β A is for Array β arguments in an Array
bind β B is for Baked β this is Baked in, for later
πΉ Arrow Functions and this β The Big Exception
Everything covered so far assumes you're using regular functions β written with the function keyword or shorthand method syntax (greet() {}).
Arrow functions behave completely differently with this.
Arrow functions do not have their own this at all. They borrow this from the scope they were written inside β not from whoever calls them. This is called lexical this.
Regular function vs arrow function β the big difference
const actor = {
name: "Rohan",
// Regular function β gets its own "this" when called
regularGreet: function() {
return "Regular: I am " + this.name;
},
// Arrow function β borrows "this" from outer scope (not the object)
arrowGreet: () => {
return "Arrow: I am " + this.name;
}
};
console.log(actor.regularGreet()); // "Regular: I am Rohan" β
console.log(actor.arrowGreet()); // "Arrow: I am undefined" β
The arrow function was written inside the object literal. But the object literal itself is in the global scope β and the global scope has no name. So the arrow function inherited undefined as its this.name.
Arrow functions also ignore call(), apply(), bind()
const arrowFn = () => {
return "I am " + this.name;
};
const rohan = { name: "Rohan" };
// Trying to override "this" with call β has NO effect on arrow functions
console.log(arrowFn.call(rohan)); // "I am undefined"
console.log(arrowFn.apply(rohan)); // "I am undefined"
const bound = arrowFn.bind(rohan);
console.log(bound()); // "I am undefined"
You cannot change an arrow function's this. It was determined at the moment it was written, and it cannot be overridden.
When arrow functions ARE the right tool
Arrow functions shine when you're inside a regular method and you want to keep the outer this:
// The old problem β "this" breaks inside forEach's callback
const actor = {
name: "Rohan",
lines: ["Good evening.", "You cannot stop me.", "Farewell."],
rehearse: function() {
// "this" here = actor β
this.lines.forEach(function(line) {
// regular function callback β "this" is undefined here
console.log(this.name + ": " + line);
});
}
};
actor.rehearse();
// "undefined: Good evening." β broken!
// The fix β arrow function inherits "this" from rehearse()
const actor = {
name: "Rohan",
lines: ["Good evening.", "You cannot stop me.", "Farewell."],
rehearse: function() {
// "this" here = actor β
this.lines.forEach((line) => {
// arrow function β inherits "this" from rehearse(), which is actor
console.log(this.name + ": " + line);
});
}
};
actor.rehearse();
// "Rohan: Good evening."
// "Rohan: You cannot stop me."
// "Rohan: Farewell."
The arrow function has no this of its own, so it uses the one from rehearse() β which is actor. This is exactly what you want.
The rule for choosing function type
| Where you're writing | Use | Why |
|---|---|---|
| Object method | Regular function or method() shorthand |
Needs its own this = the object |
| Callback inside a method (forEach, map, etc.) | Arrow function | Inherits outer method's this |
| Standalone function you'll borrow with call/apply/bind | Regular function | Arrow functions ignore call/apply/bind |
π¨ Five Mistakes Every Beginner Makes
Mistake 1 β Calling bind() and expecting it to run
function greet() {
console.log("Hello, I am " + this.name);
}
const rohan = { name: "Rohan" };
// Does nothing visible β bind() RETURNS a new fn, doesn't call it
greet.bind(rohan);
// No output at all. Nothing happened on screen.
// Store the result first, then call it
const boundGreet = greet.bind(rohan);
boundGreet(); // "Hello, I am Rohan"
// Or immediately call the result with ()
greet.bind(rohan)(); // "Hello, I am Rohan"
Mistake 2 β Passing apply() arguments as commas
function perform(role, emotion) {
console.log(this.name + " plays " + role + " feeling " + emotion);
}
const rohan = { name: "Rohan" };
// Wrong β apply() does NOT accept comma-separated args like call()
perform.apply(rohan, "villain", "furious"); // TypeError!
// apply() β wrap args in an array
perform.apply(rohan, ["villain", "furious"]);
// call() β comma-separated is fine here
perform.call(rohan, "villain", "furious");
Mistake 3 β Arrow function as an object method
const actor = {
name: "Rohan",
// Arrow function as object method
greet: () => {
return "I am " + this.name; // "this" is NOT actor
}
};
console.log(actor.greet()); // "I am undefined"
// Use regular function or shorthand
const actor2 = {
name: "Rohan",
greet() { // shorthand method β has its own this
return "I am " + this.name;
}
};
console.log(actor2.greet()); // "I am Rohan" β
Mistake 4 β Forgetting to pass thisValue
All three tools need a this value as the first argument:
function greet() {
return "Hello, " + this.name;
}
// No thisValue β "this" is undefined
greet.call(); // "Hello, undefined"
greet.call(null); // "Hello, undefined"
// Always pass the object you want as "this"
greet.call({ name: "Dev" }); // "Hello, Dev"
Mistake 5 β Losing this in a callback and not noticing
const stage = {
name: "Grand Vikram",
open: function() {
console.log(this.name + " is now open!");
}
};
// "this" is lost inside setTimeout
setTimeout(stage.open, 1000);
// "undefined is now open!"
// Fix 1 β use bind()
setTimeout(stage.open.bind(stage), 1000);
// "Grand Vikram is now open!"
// Fix 2 β wrap in an arrow function
setTimeout(() => stage.open(), 1000);
// "Grand Vikram is now open!"
// The arrow fn calls stage.open() which sets this = stage via the dot
π The Full Performance β All Three Tools Together
Let's build a complete mini-theatre system that uses every concept from this blog. Copy each block into your console, step by step.
Step 1 β The actors and standalone scripts:
// Actors β plain objects, no methods of their own
const rohan = { name: "Rohan", role: "Villain", emotion: "cold" };
const priyanka = { name: "Priyanka", role: "Hero", emotion: "brave" };
const dev = { name: "Dev", role: "Guard", emotion: "nervous"};
// Reusable performance scripts β no actor hardcoded
function introduce() {
return this.name + " plays the " + this.role;
}
function deliverLine(line) {
return this.name + " (" + this.emotion + "): \"" + line + "\"";
}
function curtainCall(applause) {
return this.name + " bows to " + applause + "/10 applause";
}
console.log("Setup complete. Scripts and actors ready.");
Step 2 β Use call() for immediate scenes:
console.log("=== ACT I β call() ===");
// Borrow introduce() for each actor
console.log(introduce.call(rohan));
// "Rohan plays the Villain"
console.log(introduce.call(priyanka));
// "Priyanka plays the Hero"
// Borrow deliverLine() with an argument
console.log(deliverLine.call(rohan, "You cannot stop what is coming."));
// "Rohan (cold): "You cannot stop what is coming.""
console.log(deliverLine.call(priyanka, "Watch me try."));
// "Priyanka (brave): "Watch me try.""
Step 3 β Use apply() when scene data is in an array:
console.log("=== ACT II β apply() ===");
// Scene data pre-packaged in arrays (like from a database)
const rohanScene = ["I will not yield. Not today."];
const priyanaScene = ["This ends now.", "For everyone."];
console.log(deliverLine.apply(rohan, rohanScene));
// "Rohan (cold): "I will not yield. Not today.""
// Multi-argument function with array data
function respond(line, targetName) {
return this.name + " to " + targetName + ": \"" + line + "\"";
}
const replyData = ["You think I fear you?", "Rohan"];
console.log(respond.apply(priyanka, replyData));
// "Priyanka to Rohan: "You think I fear you?""
Step 4 β Use bind() for the curtain call (happens later):
console.log("=== CURTAIN CALL β bind() ===");
// Create bound versions β they'll run when the moment comes
const rohanBow = curtainCall.bind(rohan);
const priyankaBow = curtainCall.bind(priyanka);
const devBow = curtainCall.bind(dev);
// Schedule them β each fires independently
setTimeout(() => console.log(rohanBow(9)), 500); // 0.5s
setTimeout(() => console.log(priyankaBow(10)), 1000); // 1.0s
setTimeout(() => console.log(devBow(6)), 1500); // 1.5s
// Output (after delays):
// "Rohan bows to 9/10 applause"
// "Priyanka bows to 10/10 applause"
// "Dev bows to 6/10 applause"
Step 5 β bind() with partial application for scene-specific functions:
// Create actor-specific versions β actor is always baked in
const rohanDelivers = deliverLine.bind(rohan);
const priyaDelivers = deliverLine.bind(priyanka);
// Just pass the line β actor is already locked
console.log(rohanDelivers("The night is mine."));
// "Rohan (cold): "The night is mine.""
console.log(priyaDelivers("Not while I breathe."));
// "Priyanka (brave): "Not while I breathe.""
console.log(rohanDelivers("Then draw your last breath."));
// "Rohan (cold): "Then draw your last breath.""
You just built a working theatre system using call(), apply(), and bind() β every tool in its right place, every actor knowing exactly who they are on every stage.
π§ Quick Reference β Everything in One Place
The golden rule
this = the object LEFT OF THE DOT when the function is called
actor.greet() β this = actor β
stage.open() β this = stage β
greet() β this = undefined β (no dot = no owner)
The three tools at a glance
// call() β run NOW, args comma by comma
fn.call(obj, arg1, arg2);
// apply() β run NOW, args in one array
fn.apply(obj, [arg1, arg2]);
// bind() β create a locked fn, run LATER
const locked = fn.bind(obj);
locked(arg1, arg2); // call whenever
Arrow function rule
// Arrow fn as object method β "this" is NOT the object
const actor = {
name: "Rohan",
greet: () => this.name // this = outer scope (usually undefined)
};
// Arrow fn INSIDE a method β inherits outer method's "this"
const actor2 = {
name: "Rohan",
rehearse() {
this.lines.forEach(line => {
console.log(this.name + ": " + line); // this = actor2 β
});
}
};
Decision guide
| I need to⦠| Use |
|---|---|
Run a function with a specific this, once |
call() |
| Run a function and my args are already in an array | apply() |
| Create a version of a function for repeated use | bind() |
Fix this lost inside setTimeout |
bind() |
Fix this lost inside a forEach callback |
Arrow function |
Write an object method that uses this |
Regular fn or shorthand |
ποΈ Today's Assignment
Open your browser console and build this step by step.
// ββ SETUP ββ
const student1 = { name: "Aryan", score: 92, subject: "JavaScript" };
const student2 = { name: "Priya", score: 88, subject: "Python" };
function report() {
return this.name + " scored " + this.score + "/100 in " + this.subject;
}
function fullReport(grade, rank) {
return this.name + ": " + this.score + " | Grade: " + grade + " | Rank: #" + rank;
}
// ββ STEP 1: call() β run report() for each student ββ
console.log(report.call(student1));
// "Aryan scored 92/100 in JavaScript"
console.log(report.call(student2));
// "Priya scored 88/100 in Python"
// ββ STEP 2: call() with arguments ββ
console.log(fullReport.call(student1, "A+", 1));
// "Aryan: 92 | Grade: A+ | Rank: #1"
// ββ STEP 3: apply() β args come from an array ββ
const priyaData = ["A", 3];
console.log(fullReport.apply(student2, priyaData));
// "Priya: 88 | Grade: A | Rank: #3"
// ββ STEP 4: bind() β create permanent versions ββ
const aryanReport = report.bind(student1);
const priyaReport = report.bind(student2);
console.log(aryanReport()); // "Aryan scored 92/100 in JavaScript"
console.log(priyaReport()); // "Priya scored 88/100 in Python"
// ββ STEP 5: bind() in a callback ββ
const delayedAryan = report.bind(student1);
setTimeout(delayedAryan, 1000);
// After 1 second: "Aryan scored 92/100 in JavaScript"
// Without bind: "undefined scored undefined/100 in undefined"
// ββ STEP 6: Arrow function test β shows why arrows are different ββ
const arrowReport = () => this.name + " scored " + this.score;
console.log(arrowReport.call(student1)); // "undefined scored undefined"
// Arrow fn ignores call() β "this" cannot be changed
// ββ BONUS: bind() with partial application ββ
function subjectScore(subject, score) {
return this.name + " scored " + score + " in " + subject;
}
const aryanInJS = subjectScore.bind(student1, "JavaScript");
console.log(aryanInJS(95)); // "Aryan scored 95 in JavaScript"
console.log(aryanInJS(88)); // "Aryan scored 88 in JavaScript"
// subject is locked. Only score changes.
π Key Takeaways
thismeans "whoever is calling this function right now" β set at call time, not write timeGolden rule:
obj.method()βthis = obj. Plainmethod()βthis = undefinedLosing context happens when a method is detached from its object β the dot connection breaks,
thisdisappearscall(obj, a, b)β run the function immediately,this = obj, pass args one by one after itapply(obj, [a, b])β run the function immediately,this = obj, pass all args in one arraybind(obj)β return a new function withthispermanently locked toobj, does NOT run immediatelyThe callback fix β
bind()is the standard solution when you pass a method tosetTimeout, an event listener, or any other callback that would otherwise losethisArrow functions have no
thisof their own β they inherit from the surrounding scope and cannot be overridden withcall/apply/bindUse arrow functions inside methods (like
forEachormapcallbacks) when you want to keep the outer object'sthisMemory trick: Call = Comma args. Apply = Array args. Bind = Baked in forever.
Enjoyed The Actor with Amnesia? Drop a reaction and share with someone learning JavaScript. See you at Episode 05. π