JavaScript's this Keyword Explained
How to control function context with call(), apply(), and bind() — with hands-on examples and common mistakes

👋 Hey, I'm Mohd Kaif – a student documenting my journey through code. I write about what I'm learning in real-time – the wins, the struggles, and the "aha!" moments. From JavaScript and React to backend systems with Node.js, databases, DevOps, TypeScript, and AI integrations. This blog is my public learning journal: honest, evolving, and always exploring. If you're curious about any of these topics, let's learn and build together!
I used to think this in JavaScript was broken.
Not "tricky." Not "nuanced." Broken. Like the language designers had a meeting, decided to make one keyword mean six different things depending on the weather, and called it a feature.
Sound familiar? Maybe you've been there too:
You write a method inside an object, call it somewhere else, and
thisis suddenlyundefinedYou copy a function from one object to another and it stops working for no obvious reason
You've seen
call(),apply(), andbind()in documentation or Stack Overflow answers — but you skip them because they look intimidatingYou've fixed a
thisbug by accident and weren't sure why your fix worked
Here's the thing: the problem isn't you. this is genuinely confusing if nobody explains the single rule that governs it. Most tutorials dump five rules on you at once, and your brain quits.
This article teaches you that one rule — and then shows you three powerful tools that put you in full control of this.
✅ What You'll Learn
Why
thisbehaves differently depending on how you call a function (not where you write it)How
call()lets you invoke any function with a customthison the spotHow
apply()does the same thing — with one key difference for handling argumentsHow
bind()creates a permanently locked version of a function you can reuse anywhereWhen to use each one, with real-world scenarios you'll actually encounter
What mistakes to watch out for — and how to recognize them before they bite you
No deep theory required. If you know what a function and an object are in JavaScript, you're ready.
The One Rule That Explains Everything
Before we look at any code, here's the mental model that unlocks all of this:
thisrefers to whoever is calling the function — not where the function was written.
Read that again. It's worth it.
this is not set when you define a function. It's set when you call it. The caller determines the context. That's it. Everything else — call, apply, bind, arrow functions — is just a consequence of this one idea.
this in a Plain Function Call
Let's start at the simplest possible case.
function greet() {
console.log(this);
}
greet();
Who's calling greet() here? Nobody specific — you just called it loose, on its own. So this has no meaningful owner.
In non-strict mode (browser):
thisdefaults to the globalwindowobjectIn strict mode:
thisisundefined
"use strict";
function greet() {
console.log(this); // undefined
}
greet();
The takeaway: A function called by itself has no caller, so this is either the global object or undefined. Not very useful — and often the source of bugs.
this Inside an Object Method
Now things get more interesting.
const user = {
name: "Rahul",
greet: function () {
console.log("Hello, I am " + this.name);
},
};
user.greet(); // Hello, I am Rahul
Why does this work? Because user is the caller. You wrote user.greet() — so JavaScript sets this = user before running the function.
Think of it like this:
user → calls greet() → this = user ✅
Simple. But here's where developers get tripped up — what happens when you detach the function?
const fn = user.greet;
fn(); // Hello, I am undefined ❌
You took the same function and called it without an owner. Now this is lost. The function didn't change — the caller did.
This is the most common this bug in JavaScript. Memorize this pattern.
🛠️ Quick Challenge #1
Before moving on, try this in your browser console or a code sandbox:
const car = {
brand: "Toyota",
describe: function () {
console.log("This is a " + this.brand);
},
};
const detached = car.describe;
car.describe(); // What does this print?
detached(); // What does this print? Why?
Predict the output before running it. If you got it right, the mental model is clicking. If not — re-read the section above and try again.
Introducing call(), apply(), and bind()
So you have a function. You want to control what this is when it runs. That's exactly what these three methods do.
Think of them as a steering wheel for this. Instead of letting JavaScript guess who the caller is, you tell it explicitly.
Let's walk through each one.
call() — Invoke Immediately, Pass Arguments One by One
call() runs a function right away, with a this value you choose.
Syntax:
functionName.call(thisArg, arg1, arg2, ...);
Example:
const person1 = { name: "Rahul" };
const person2 = { name: "Aman" };
function greet(city) {
console.log(`Hi, I am \({this.name} from \){city}`);
}
greet.call(person1, "Delhi"); // Hi, I am Rahul from Delhi
greet.call(person2, "Mumbai"); // Hi, I am Aman from Mumbai
The same function, two different callers, two different results. No duplication. No separate methods on each object.
Key idea: call() executes immediately. Arguments go in one at a time after thisArg.
Method Borrowing — The Most Practical Use of call()
Here's a real pattern you'll use in actual codebases. Imagine person1 has a method, and you want person2 to use it — without copying the function.
const person1 = {
name: "Rahul",
greet: function () {
console.log(`Hello, I am ${this.name}`);
},
};
const person2 = {
name: "Aman",
};
person1.greet.call(person2); // Hello, I am Aman
You borrowed person1's method and ran it as if it belonged to person2. This is called method borrowing, and it's one of the most elegant patterns in JavaScript.
apply() — Same as call(), But Arguments Go in an Array
apply() is nearly identical to call(). The only difference: instead of listing arguments one by one, you pass them as a single array.
Syntax:
functionName.apply(thisArg, [arg1, arg2, ...]);
Example:
const person = { name: "Rahul" };
function greet(city, country) {
console.log(`Hi, I am \({this.name} from \){city}, ${country}`);
}
greet.apply(person, ["Delhi", "India"]);
// Hi, I am Rahul from Delhi, India
When is this useful? When your arguments are already in an array — from an API response, user input, or another function — you don't have to unpack them manually.
const args = ["Delhi", "India"];
greet.apply(person, args); // Works perfectly ✅
🛠️ Quick Challenge #2
You have this setup:
const product = { name: "Laptop" };
function describe(price, currency) {
console.log(`\({this.name} costs \){currency}${price}`);
}
const details = [999, "$"];
Call describe using:
call()— passing the arguments individuallyapply()— passing thedetailsarray directly
Both should print: Laptop costs $999
bind() — Create a New Function with this Locked In
bind() is where things get really powerful — and where most people get confused at first.
Unlike call() and apply(), bind() does not run the function. It returns a new function with this permanently fixed to whatever you pass in.
Syntax:
const newFunction = functionName.bind(thisArg, arg1, arg2, ...);
Example:
const person = { name: "Rahul" };
function greet(city) {
console.log(`Hi, I am \({this.name} from \){city}`);
}
const boundGreet = greet.bind(person, "Delhi");
// Nothing has run yet. boundGreet is just a new function.
boundGreet(); // Hi, I am Rahul from Delhi ✅
You can also call it later, store it, pass it around — and this will always be person, no matter where it gets called from.
Why does this matter? Two huge real-world scenarios:
1. Event Handlers
const user = {
name: "Rahul",
handleClick: function () {
console.log(`${this.name} clicked the button`);
},
};
// Without bind — this = the button DOM element ❌
button.addEventListener("click", user.handleClick);
// With bind — this = user ✅
button.addEventListener("click", user.handleClick.bind(user));
2. Callbacks and Timers
const timer = {
message: "Time's up!",
start: function () {
setTimeout(function () {
console.log(this.message); // undefined ❌ — 'this' is lost
}, 1000);
setTimeout(function () {
console.log(this.message); // "Time's up!" ✅
}.bind(this), 1000);
},
};
timer.start();
🛠️ Quick Challenge #3
Take the car object from Challenge #1 and do all three:
const car = {
brand: "Toyota",
describe: function (color) {
console.log(`This is a \({color} \){this.brand}`);
},
};
const newCar = { brand: "Honda" };
Use
call()to rundescribewithnewCarasthis, passing"red"as the colorUse
apply()to do the same, but pass the color inside an arrayUse
bind()to create a new functionhondaDescribethat always usesnewCarasthis, then call it with"blue"
call vs apply vs bind — The Full Comparison
call() |
apply() |
bind() |
|
|---|---|---|---|
| Executes immediately? | ✅ Yes | ✅ Yes | ❌ No |
| Returns | Function result | Function result | New function |
| How arguments are passed | Individually: (ctx, a, b) |
As array: (ctx, [a, b]) |
Individually: (ctx, a, b) |
| Best used for | One-off calls with custom this |
When args are already in an array | Callbacks, event handlers, reusable functions |
A simple way to remember the difference between call and apply:
Call → Comma-separated arguments Apply → Array arguments
The Mistakes That Will Get You (And How to Avoid Them)
Mistake 1: Losing this When Detaching a Method
const obj = {
name: "Rahul",
greet: function () {
console.log(this.name);
},
};
const fn = obj.greet;
fn(); // undefined ❌
Why it happens: fn() is called without an owner, so JavaScript has no caller to assign to this. The function is the same — the calling context changed.
Fix it:
fn.call(obj); // immediate fix
const safe = obj.greet.bind(obj); // permanent fix
safe();
Mistake 2: Expecting bind() to Execute
greet.bind(person); // ❌ Nothing happens — you forgot to call it!
const bound = greet.bind(person);
bound(); // ✅ Now it runs
bind() is a factory — it makes a function. You still have to invoke what it gives you.
Mistake 3: Mixing Up Argument Format
greet.call(person, ["Delhi", "India"]); // ❌ args become a nested array
greet.apply(person, "Delhi"); // ❌ apply expects an array
Fix:
greet.call(person, "Delhi", "India"); // ✅ individual args
greet.apply(person, ["Delhi", "India"]); // ✅ array of args
What to Learn Next
Understanding this is a milestone — and it connects directly to several powerful JavaScript concepts:
Arrow Functions and
this: Arrow functions don't have their ownthisat all — they inherit it from the surrounding scope. This makes them behave very differently from regular functions, and it's a crucial distinction in modern JavaScript.classsyntax and methods: JavaScript classes rely onthisthroughout. Now that you understand howthisworks, class methods and constructors will make much more sense.Closures: Closures and
thisinteract in subtle ways — especially inside callbacks and timers. Understanding one deepens your understanding of the other.Prototype chain: Method borrowing with
call()is closely related to how JavaScript's prototype system works under the hood. Once you're comfortable withthis, prototypes are a natural next step.
💬 Got Questions?
Drop a comment below — I'd love to hear about a this-related bug you've encountered, or help you work through something that's still fuzzy.
Here are topics coming up in future articles:
Arrow Functions and
this: Why arrows don't have their ownthis, and when that saves you vs. trips you upJavaScript Prototypes Explained: How objects inherit from each other, and why understanding
thismakes it easierClosures in Plain English: What closures actually are, why they exist, and the patterns you'll see in real codebases
Async/Await Deep Dive: Writing clean asynchronous code and the common pitfalls that break it
Found this helpful? Share it with someone who's been burned by a this bug — you might save them hours of debugging. And if you want more articles like this, follow along so you don't miss the next one.




