Skip to main content

Command Palette

Search for a command to run...

Mastering JavaScript: this, call(), apply() & bind() Explained

Updated
15 min readView as Markdown

1. What Is this in JavaScript?

Rahul is a normal guy. Same person, same face, same brain.

But call him from different places and he responds with a completely different identity.

  • At home → Mom calls him "Munna"

  • At school → Teachers call him "Rahul Kumar"

  • In his friend circle → Everyone calls him "Rocky"

He never changes. Only who is calling him changes.

That is exactly what this is in JavaScript.

Case 1 — Global Context (Browser)

In a browser, when you're outside everything no function, no object this is the window itself.

console.log(this); // window
console.log(this === window); // true

Rahul is standing in the middle of the city. No home, no school, no friends around. The city itself (window) owns him.

Case 2 — Global Context (Outside Browser / Node.js)

In Node.js, the global object is not window it's an empty module object {} at the top level.

console.log(this); // {}  ← empty module object in Node.js
console.log(this === global); // false  ← surprising!

Same Rahul, different city. The rules of the city changed.

Inside a Node.js function though, this becomes the global object — not {}.

Case 3 — Normal Function (Non-Strict Mode)

Call a plain function with no owner this defaults to the global object.

function whereAmI() {
  console.log(this); // window (browser) or global (Node)
}

whereAmI();

Rahul got called but nobody claimed him. So the entire city (window/global) becomes his default guardian.


Case 4 — Normal Function (Strict Mode)

Add 'use strict' and everything tightens up. Now this is undefined inside a plain function call.

'use strict';

function whereAmI() {
  console.log(this); // undefined
}

whereAmI();

Strict mode says — "If nobody owns you, you belong to nobody." Rahul has no identity here. This actually prevents bugs because you can't accidentally modify the global object.

Case 5 — Inside an Object Method

When a function is a property of an object and you call it via that object, this is that object.

const home = {
  name: 'Munna',
  introduce() {
    console.log(`Hi I'm ${this.name}`); // this = home
  }
};

home.introduce(); // Hi I'm Munna

Mom called. So inside that context, Rahul is Munna. The object before the dot is always this.

Case 6 — Normal Function Inside a Method

This is where most developers get surprised. A regular function nested inside a method loses the object's this.

const home = {
  name: 'Munna',
  introduce() {
    console.log(this.name); // Munna ✅

    function innerFn() {
      console.log(this.name); // undefined ❌
    }

    innerFn(); // nobody called this with 'home'
  }
};

home.introduce();

Rahul is Munna when Mom calls him. But the moment a random inner voice speaks — it doesn't belong to Mom anymore. It lost the context.

Case 7 — Arrow Function (Standalone)

Arrow functions never have their own this. They look up to their parent scope and borrow whatever this is there.

const arrowFn = () => {
  console.log(this); // window (borrows from global scope)
};

arrowFn();

Rahul as an arrow function has no identity of his own. He just looks at whoever raised him and says "I'll be whatever you are."

Case 8 — Arrow Function Inside a Method

This is the hero move. Arrow functions fix the problem from Case 6 automatically — because they borrow this from the method they're written inside.

const home = {
  name: 'Munna',
  introduce() {
    console.log(this.name); // Munna ✅

    const innerArrow = () => {
      console.log(this.name); // Munna ✅ — borrows from introduce()
    };

    innerArrow();
  }
};

home.introduce();

The arrow function looks at its parent — introduce() — and says "Your this is my this." No confusion. Rahul stays Munna throughout.

Case 9 — Arrow Function as a Method (Don't Do This)

This looks fine but breaks badly.

const home = {
  name: 'Munna',
  introduce: () => {
    console.log(this.name); // undefined ❌
  }
};

home.introduce();

┌──────────────────────────────────────────────
│  Where is 'this'?          │  What does 'this' become? │
├──────────────────────────────────────────────
│  Global (Browser)          │  window                   │
│  Global (Node.js)          │  {}  (module object)      │
│  Normal fn, non-strict     │  window / global          │
│  Normal fn, strict mode    │  undefined                │
│  Object method             │  that object              │
│  Normal fn inside method   │  window / undefined ❌    │
│  Arrow fn (standalone)     │  borrows from parent      │
│  Arrow fn inside method    │  same as method ✅        │
│  Arrow fn as method        │  window / undefined ❌    │
└─────────────────────────────────────────────

call() — Borrowing Identity on Demand


Remember Rahul? He has one introduce function. But different worlds want to use it — home, school, friends.

Without call(), each world would need to write its own introduce function. That's repetition. That's wasteful.

call() says — "Use this one function, but run it as if it belongs to whoever I say."

The Syntax

functionName.call(thisArg, arg1, arg2, arg3, ...);
  • thisArg → who should this be inside the function

  • arg1, arg2... → normal arguments, passed one by one

  • runs immediately

Case 1 — Basic call(), No Arguments

One function. Three worlds. Zero repetition.

function introduce() {
  console.log(`Hi, I'm ${this.name}`);
}

const home         = { name: 'Munna' };
const school       = { name: 'Rahul Kumar' };
const friendCircle = { name: 'Rocky' };

introduce.call(home);
// Hi, I'm Munna

introduce.call(school);
// Hi, I'm Rahul Kumar

introduce.call(friendCircle);
// Hi, I'm Rocky

Same function. this switches based on what you pass into call().

Case 2 — call() With Arguments

Now Rahul doesn't just introduce himself — he also tells where he is and what he's doing there.

function introduce(place, activity) {
  console.log(`I'm \({this.name}, I'm at \){place} and I'm ${activity}`);
}

const home         = { name: 'Munna' };
const school       = { name: 'Rahul Kumar' };
const friendCircle = { name: 'Rocky' };

introduce.call(home, 'kitchen', 'eating Mom\'s food');
// I'm Munna, I'm at kitchen and I'm eating Mom's food

introduce.call(school, 'classroom', 'solving math');
// I'm Rahul Kumar, I'm at classroom and I'm solving math

introduce.call(friendCircle, 'park', 'playing cricket');
// I'm Rocky, I'm at park and I'm playing cricket

Arguments go after thisArg, one by one, separated by commas.

Case 3 — Borrowing a Method from Another Object

This is where call() really shines. One object has a method. Another object wants to use it without rewriting it.

const home = {
  name: 'Munna',
  introduce() {
    console.log(`Hi, I'm ${this.name} and I belong here`);
  }
};

const school = { name: 'Rahul Kumar' };

// school doesn't have introduce() — but it can borrow it!
home.introduce.call(school);
// Hi, I'm Rahul Kumar and I belong here

School borrowed home's introduce method. Rahul walked into school and introduced himself as Rahul Kumar — not Munna.

Case 4 — call() With null

What if you don't care about this at all? Pass null.

function add(a, b) {
  console.log(a + b);
}

add.call(null, 5, 10); // 15

this becomes the global object (or undefined in strict mode). You're essentially just using call() to pass arguments which is fine when the function doesn't use this at all.

apply() — Same as call(), But Arguments Come in a Box


Rahul still has the same one function. apply() works exactly like call() — same idea, same result.

The only difference?

  • call() → you hand arguments one by one like handing someone items individually

  • apply() → you pack all arguments in an array and hand the whole box at once

That's it. One difference. Everything else is identical.

The Syntax

functionName.apply(thisArg, [arg1, arg2, arg3, ...]);
  • thisArg → who should this be — same as call()

  • [arg1, arg2...] → arguments packed inside an array

  • runs immediately — same as call()

Side by Side — call() vs apply()

function introduce(place, activity) {
  console.log(`I'm \({this.name}, at \){place}, ${activity}`);
}

const friendCircle = { name: 'Rocky' };

// call() — arguments one by one
introduce.call(friendCircle, 'park', 'playing cricket');

// apply() — arguments in an array
introduce.apply(friendCircle, ['park', 'playing cricket']);

// Both print exactly the same thing:
// I'm Rocky, at park, playing cricket

Same output. Different packaging.

Case 1 — Basic apply()

function introduce(place, activity) {
  console.log(`I'm \({this.name}, I'm at \){place} and I'm ${activity}`);
}

const home         = { name: 'Munna' };
const school       = { name: 'Rahul Kumar' };
const friendCircle = { name: 'Rocky' };

introduce.apply(home, ['kitchen', 'eating Mom\'s food']);
// I'm Munna, I'm at kitchen and I'm eating Mom's food

introduce.apply(school, ['classroom', 'solving math']);
// I'm Rahul Kumar, I'm at classroom and I'm solving math

introduce.apply(friendCircle, ['park', 'playing cricket']);
// I'm Rocky, I'm at park and I'm playing cricket

Case 2 — Where apply() Wins Over call()

The real power of apply() comes when your arguments are already sitting in an array. You don't need to unpack them manually.

function introduce(place, activity) {
  console.log(`I'm \({this.name}, at \){place}, ${activity}`);
}

const friendCircle = { name: 'Rocky' };

// Arguments already in an array — maybe from an API, a form, anywhere
const details = ['park', 'playing cricket'];

// ❌ With call() — you'd have to unpack manually
introduce.call(friendCircle, details[0], details[1]);

// ✅ With apply() — just pass the array directly
introduce.apply(friendCircle, details);

No unpacking. No indexing. Just pass the box.

Case 3 — apply() With a Dynamic List

Rahul is at school. The subjects he's studying change every day. They come as an array.

function studyingToday(sub1, sub2, sub3) {
  console.log(`\({this.name} is studying \){sub1}, \({sub2} and \){sub3} today`);
}

const school = { name: 'Rahul Kumar' };

const mondaySubjects  = ['Math', 'Science', 'English'];
const tuesdaySubjects = ['Hindi', 'History', 'Computer'];

studyingToday.apply(school, mondaySubjects);
// Rahul Kumar is studying Math, Science and English today

studyingToday.apply(school, tuesdaySubjects);
// Rahul Kumar is studying Hindi, History and Computer today

The array changes. You just pass it in. No rewriting anything.


Case 4 — Classic apply() Trick with Math

apply() has a famous use case — passing an array to functions that don't normally accept arrays.

const marks = [88, 95, 72, 100, 63];

// Math.max doesn't accept arrays directly
console.log(Math.max(marks)); // NaN ❌

// apply() spreads the array into individual arguments
console.log(Math.max.apply(null, marks)); // 100 ✅
console.log(Math.min.apply(null, marks)); // 63  ✅

bind() — Save the Identity for Later


call() runs immediately. apply() runs immediately.

bind() says — "Don't run yet. Just remember who this should be, and give me a new function I can use whenever I want."

Think of it like this —

  • call() → Rahul walks into school right now and introduces himself

  • apply() → Rahul walks into school right now with a packed bag of things to say

  • bind() → Rahul gets a school ID card made. He can use it anytime he wants to enter school. The identity is locked in permanently.

The Syntax

const newFunction = functionName.bind(thisArg, arg1, arg2, ...);

// call it later, whenever you want
newFunction();
  • thisArg → who this should be — locked in permanently

  • returns a brand new function — does NOT run immediately

  • you call that new function whenever you're ready


Case 1 — Basic bind()

function introduce() {
  console.log(`Hi, I'm ${this.name}`);
}

const home         = { name: 'Munna' };
const school       = { name: 'Rahul Kumar' };
const friendCircle = { name: 'Rocky' };

// Create bound versions — not called yet
const introduceMunna  = introduce.bind(home);
const introduceRahul  = introduce.bind(school);
const introduceRocky  = introduce.bind(friendCircle);

// Call them whenever you want
introduceMunna();  // Hi, I'm Munna
introduceRahul();  // Hi, I'm Rahul Kumar
introduceRocky();  // Hi, I'm Rocky

Three ID cards made. Each locked to a different identity. Use them anytime.


Case 2 — bind() With Arguments

You can pre-fill arguments too — not just this.

function introduce(place, activity) {
  console.log(`I'm \({this.name}, at \){place}, ${activity}`);
}

const friendCircle = { name: 'Rocky' };

// Lock in both 'this' AND arguments
const rockyAtPark = introduce.bind(friendCircle, 'park', 'playing cricket');

// Call it later — no need to pass arguments again
rockyAtPark();
// I'm Rocky, at park, playing cricket

rockyAtPark();
// I'm Rocky, at park, playing cricket

rockyAtPark();
// I'm Rocky, at park, playing cricket — same every time

Once bound, the identity and arguments are frozen. Call it 10 times, same result.

Case 3 — Partial Application (Pre-fill Some Arguments)

You can bind this and only some arguments — and fill the rest later when calling.

function introduce(place, activity) {
  console.log(`I'm \({this.name}, at \){place}, ${activity}`);
}

const school = { name: 'Rahul Kumar' };

// Lock in 'this' and first argument only
const rahulAtSchool = introduce.bind(school, 'classroom');

// Fill the remaining argument later
rahulAtSchool('solving math');
// I'm Rahul Kumar, at classroom, solving math

rahulAtSchool('giving exam');
// I'm Rahul Kumar, at classroom, giving exam

rahulAtSchool('presenting project');
// I'm Rahul Kumar, at classroom, presenting project

Place is locked. Activity changes every time. That flexibility is called partial application.

bind() Returns a New Function (Not a Result)

This is the key thing to remember.

function introduce() {
  console.log(`Hi I'm ${this.name}`);
}

const home = { name: 'Munna' };

// call() → runs and returns the result
introduce.call(home); // runs immediately ✅

// bind() → does NOT run, returns a new function
const boundFn = introduce.bind(home);
console.log(boundFn); // [Function: bound introduce]
boundFn(); // now it runs ✅
```

`bind()` gives you a function. You decide when to fire it.

call() vs apply() vs bind()

Same Rahul. Same function. Three different ways to control this.

Let's put them all in one place and kill the confusion forever.

Same Task — Three Ways

function introduce(place, activity) {
  console.log(`I'm \({this.name}, at \){place}, ${activity}`);
}

const friendCircle = { name: 'Rocky' };

// call() — args one by one, runs NOW
introduce.call(friendCircle, 'park', 'playing cricket');

// apply() — args in array, runs NOW
introduce.apply(friendCircle, ['park', 'playing cricket']);

// bind() — args one by one, runs LATER
const rockyIntro = introduce.bind(friendCircle, 'park', 'playing cricket');
rockyIntro();

// All three print:
// I'm Rocky, at park, playing cricket
```

Same destination. Three different vehicles.

---

## The Core Difference — One Line Each
```
call()  →  set this + pass args separately  + run immediately
apply() →  set this + pass args as array    + run immediately
bind()  →  set this + pass args separately  + return new function
```

---

## The Deep Comparison Table

┌──────────────────┬───────────────┬──────────
│    Feature       │    call()     │   apply()     │    bind()     
├──────────────────┼─────────────────────────
│ Runs immediately │     ✅ Yes    │    ✅ Yes     │     ❌ No     
│ Returns          │  fn result   │   fn result   │  new function 
│ Arguments format │  one by one  │  in an array  │  one by one   
│ Can reuse?       │     ❌ No    │    ❌ No      │     ✅ Yes    
│ Partial apply?   │     ❌ No    │    ❌ No      │     ✅ Yes   
│ this permanent?  │     ❌ No    │    ❌ No      │     ✅ Yes    
│ Good for         │   borrowing  │  array args   │  callbacks   
│                  │   methods    │  Math tricks  │  event handler
└──────────────────┴───────────────┴───────────

Full Side by Side — Everything Together

function introduce(place, activity) {
  console.log(`I'm \({this.name}, at \){place}, ${activity}`);
}

const home         = { name: 'Munna' };
const school       = { name: 'Rahul Kumar' };
const friendCircle = { name: 'Rocky' };

// ── call() ──────────────────────────────────────
introduce.call(home,         'kitchen',   'eating');
introduce.call(school,       'classroom', 'studying');
introduce.call(friendCircle, 'park',      'playing');
// all run immediately ↑

// ── apply() ─────────────────────────────────────
introduce.apply(home,         ['kitchen',   'eating']);
introduce.apply(school,       ['classroom', 'studying']);
introduce.apply(friendCircle, ['park',      'playing']);
// all run immediately ↑

// ── bind() ──────────────────────────────────────
const atHome   = introduce.bind(home,         'kitchen',   'eating');
const atSchool = introduce.bind(school,       'classroom', 'studying');
const atPark   = introduce.bind(friendCircle, 'park',      'playing');

// nothing ran yet ↑ — run them when ready ↓
atHome();    // I'm Munna, at kitchen, eating
atSchool();  // I'm Rahul Kumar, at classroom, studying
atPark();    // I'm Rocky, at park, playing

Conclusion

this is not complicated it simply means who is calling the function at that moment, just like Rahul responds to a different name depending on where he is. call() and apply() let you borrow any function and run it immediately with a this of your choice the only difference being how you pack the arguments. bind() takes it a step further by locking the identity permanently and returning a new function you can carry and use whenever the moment is right. Together, these three give you complete control over context making your code reusable, clean, and free from the classic this confusion. Master these and you don't just understand JavaScript better you start thinking in JavaScript

More from this blog