Skip to main content

Command Palette

Search for a command to run...

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.

Updated
β€’29 min read
The Magic of this, call(), apply(), and bind() in JavaScript
J

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 this is and why JavaScript has it

  • Why this gives different answers in different situations

  • The "left of the dot" rule β€” the one rule that explains everything

  • What call() does, step by step, with story

  • What apply() does and when arrays make it cleaner

  • What bind() does and why callbacks need it

  • Arrow functions and this β€” the one big exception

  • 5 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:

  1. We created an object called actor with a name and a greet function

  2. Inside greet, we wrote this.name

  3. We called actor.greet()

  4. JavaScript said: "The object calling this function is actor. So this = actor. So this.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.

What is this β€” the actor reads the stage board

πŸ“ 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 the call method that every function has

  • thisValue β€” the object you want this to point to inside the function

  • arg1, 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, with this = 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 like call() β€” run the function right now with a specific this β€” 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 with this (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

  • this means "whoever is calling this function right now" β€” set at call time, not write time

  • Golden rule: obj.method() β†’ this = obj. Plain method() β†’ this = undefined

  • Losing context happens when a method is detached from its object β€” the dot connection breaks, this disappears

  • call(obj, a, b) β€” run the function immediately, this = obj, pass args one by one after it

  • apply(obj, [a, b]) β€” run the function immediately, this = obj, pass all args in one array

  • bind(obj) β€” return a new function with this permanently locked to obj, does NOT run immediately

  • The callback fix β€” bind() is the standard solution when you pass a method to setTimeout, an event listener, or any other callback that would otherwise lose this

  • Arrow functions have no this of their own β€” they inherit from the surrounding scope and cannot be overridden with call/apply/bind

  • Use arrow functions inside methods (like forEach or map callbacks) when you want to keep the outer object's this

  • Memory 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. 🎭