`this` in JavaScript: The Complete Guide
Stop guessing what 'this' refers to. Master calling context, arrow functions, ES6 classes, 'call', 'apply', and 'bind' — with hands-on exercises.

👋 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!
You know that feeling when your code looks completely fine — but it still breaks?
You wrote the function. You put it inside the object. You called it. And somehow, this.name comes back undefined, and you're left staring at the screen wondering what went wrong.
If that sounds familiar, you're probably dealing with JavaScript's most misunderstood keyword: this.
Have you ever called a method on an object and gotten
undefinedfor no obvious reason?Have you ever passed a function as a callback and suddenly lost access to your object's data?
Have you ever fixed a
thisbug by accident — and had no idea why it worked?Have you ever wondered why the same function behaves differently depending on where you call it?
The problem isn't that you're bad at JavaScript. The problem is that this breaks every expectation you bring from other languages — and most tutorials never explain the one rule that makes it all predictable.
If you've ever felt confused by this, this article is exactly for you.
✅ What You'll Learn
What
thisactually is — and the one mental model that makes it clickHow
thisbehaves in global scope, inside objects, and inside functionsWhy the same function can produce different
thisvalues depending on how it's calledHow arrow functions changed everything about
thisin modern JavaScriptHow to take full control of
thisusingcall(),apply(), andbind()How
thisworks inside classes — essential for real-world applications
No deep theory. No execution context internals. Just clear rules and real examples.
The One Rule That Governs Everything
Before diving into examples, here's the single insight that makes this predictable:
thisis determined by who calls the function — not where the function is written.
Read that again. It's not about where the function lives. It's not about where it was defined. It's about the caller.
Caller → Function → this = Caller
Every time you're confused about this, just ask one question:
👉 "Who is calling this function right now?"
The answer to that question is this. Keep this rule in your head as you read through every example below — it will always hold.
this in the Global Context
The simplest case. No objects, no functions — just this sitting at the top level.
In a Browser
console.log(this); // window
The global object in the browser is window, so this points to it.
In Node.js
console.log(this); // {}
In Node.js, the top-level this is module.exports — which starts as an empty object.
In Strict Mode
"use strict";
function show() {
console.log(this); // undefined
}
show();
Strict mode is important. Without an explicit caller, JavaScript no longer defaults this to the global object — it becomes undefined instead. This is actually safer, because it forces you to be intentional.
| Context | this value |
|---|---|
| Browser (global) | window |
| Node.js (global) | {} (module.exports) |
| Strict mode function | undefined |
this Inside Objects
This is where this becomes genuinely useful. Let's use a user object that we'll build on throughout this article.
const user = {
name: "Aman",
greet: function () {
console.log(`Hello, I'm ${this.name}`);
}
};
user.greet(); // Hello, I'm Aman
Apply the rule: who calls greet()? The object user does. So this = user, and this.name gives us "Aman".
user → greet()
this = user ✅
Now let's see what happens when we detach the method:
const fn = user.greet;
fn(); // Hello, I'm undefined
Same function. Different result. Why?
fn() ← no object calling it
this = window (or undefined in strict mode) ❌
When you pull a method off an object and call it standalone, you lose the caller. The function didn't change — the calling context did.
Key lesson: Detaching a method from its object strips away its
thiscontext.
🏋️ Exercise 1
Predict the output of this code before running it:
const counter = {
count: 10,
increment: function () {
console.log(this.count + 1);
}
};
const standalone = counter.increment;
counter.increment(); // What prints here?
standalone(); // What about here?
See Answer
counter.increment()→11(caller iscounter,this.countis10)standalone()→NaN(caller iswindow,window.countisundefined,undefined + 1isNaN)
How Calling Context Changes this
This is the core of everything. The same function can produce completely different this values — all depending on how it's invoked.
function greet() {
console.log(`Hi, I'm ${this.name}`);
}
const user1 = { name: "Aman", greet };
const user2 = { name: "Riya", greet };
user1.greet(); // Hi, I'm Aman
user2.greet(); // Hi, I'm Riya
greet(); // Hi, I'm undefined (or error in strict mode)
Three calls. Three different this values. Same function.
user1 → greet() → this = user1
user2 → greet() → this = user2
greet() → this = window
This is this in its purest form — dynamic, runtime-determined, and entirely dependent on the caller.
The setTimeout Trap
One of the most common real-world bugs:
const user = {
name: "Aman",
greet: function () {
console.log(`Hello, I'm ${this.name}`);
}
};
setTimeout(user.greet, 1000); // Hello, I'm undefined
Why? When you pass user.greet to setTimeout, you're handing over the function reference — detached from user. After one second, setTimeout calls the function, and setTimeout is the caller now, not user. The this context is lost.
This exact bug appears constantly in event handling, API callbacks, and timers. Keep it in mind.
Arrow Functions: A Different Kind of this
Modern JavaScript introduced arrow functions, and they fundamentally changed how this works. This is where many developers get tripped up.
Arrow functions do not have their own
this. They inheritthisfrom the surrounding scope where they were defined.
Compare these two:
const user = {
name: "Aman",
greetRegular: function () {
console.log(this.name); // "Aman" — this = user
},
greetArrow: () => {
console.log(this.name); // undefined — this = window (outer scope)
}
};
user.greetRegular(); // Aman ✅
user.greetArrow(); // undefined ❌
The arrow function is defined inside the object literal, but object literals don't create a new this scope. So the arrow function looks outward to the enclosing scope — which is the global scope — and this becomes window.
When Arrow Functions Are Your Best Friend
Arrow functions shine when you're working inside a regular function and want to preserve this:
const user = {
name: "Aman",
friends: ["Riya", "Karan"],
listFriends: function () {
// 'this' here is user ✅
this.friends.forEach((friend) => {
// Arrow function inherits 'this' from listFriends
console.log(`\({this.name} knows \){friend}`);
});
}
};
user.listFriends();
// Aman knows Riya
// Aman knows Karan
If you used a regular function inside forEach, this would break. The arrow function borrows this from listFriends — which is correctly bound to user.
Arrow Function this Rule
Arrow function → looks outward → inherits this from enclosing scope
| Function Type | this determined by |
|---|---|
| Regular function | Who calls it (runtime) |
| Arrow function | Where it's defined (lexical) |
🏋️ Exercise 2
What will this print?
const timer = {
seconds: 0,
start: function () {
setInterval(() => {
this.seconds++;
console.log(this.seconds);
}, 1000);
}
};
timer.start();
See Answer
It prints 1, 2, 3, ... every second, incrementing correctly.
Because setInterval receives an arrow function, which inherits this from start(). And start() is called by timer, so this = timer. The arrow function correctly accesses timer.seconds.
If you replaced the arrow function with a regular function, this would be window and window.seconds would be NaN.
Taking Control: call(), apply(), and bind()
So far, this has been decided by the caller automatically. But JavaScript gives you three tools to explicitly set this yourself.
call() — Call with a specific this, arguments one by one
function introduce(role, company) {
console.log(`I'm \({this.name}, a \){role} at ${company}.`);
}
const user = { name: "Aman" };
introduce.call(user, "developer", "OpenAI");
// I'm Aman, a developer at OpenAI.
call() immediately invokes the function with this set to user.
apply() — Same as call(), but arguments as an array
introduce.apply(user, ["developer", "OpenAI"]);
// I'm Aman, a developer at OpenAI.
Useful when your arguments are already in an array.
bind() — Returns a new function with this permanently locked
const boundIntroduce = introduce.bind(user);
boundIntroduce("developer", "OpenAI");
// I'm Aman, a developer at OpenAI.
bind() doesn't call the function immediately. It creates a new function where this is permanently set to user — no matter how or where that new function is called later.
This is the fix for the setTimeout bug we saw earlier:
setTimeout(user.greet.bind(user), 1000);
// Hello, I'm Aman ✅
Quick Reference
| Method | Calls immediately? | Arguments |
|---|---|---|
call(obj, a, b) |
✅ Yes | Comma-separated |
apply(obj, [a, b]) |
✅ Yes | Array |
bind(obj) |
❌ No (returns new fn) | Comma-separated later |
🏋️ Exercise 3
Fix this broken code using bind():
const wallet = {
balance: 500,
showBalance: function () {
console.log(`Balance: $${this.balance}`);
}
};
const show = wallet.showBalance;
show(); // Balance: $undefined ❌
// Your fix here — make it print "Balance: $500"
See Answer
const show = wallet.showBalance.bind(wallet);
show(); // Balance: $500 ✅
this Inside Classes
Classes in JavaScript are built on top of the same this system — which means the same rules apply. Understanding this protects you from a very common class-related bug.
class User {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello, I'm ${this.name}`);
}
}
const user = new User("Aman");
user.greet(); // Hello, I'm Aman ✅
When you call user.greet(), user is the caller, so this = user. Clean and predictable.
The Class Method Detachment Bug
But the same trap from earlier hits classes too:
const greetFn = user.greet;
greetFn(); // Hello, I'm undefined ❌
And it's especially dangerous with event listeners:
document.querySelector("button").addEventListener("click", user.greet);
// Prints: Hello, I'm undefined ❌
// Because the button is now the caller, not user
The Fix: Arrow Class Fields
Modern JavaScript lets you define methods as arrow function properties, which permanently binds this at the time the class instance is created:
class User {
constructor(name) {
this.name = name;
}
greet = () => {
console.log(`Hello, I'm ${this.name}`);
}
}
const user = new User("Aman");
document.querySelector("button").addEventListener("click", user.greet);
// Hello, I'm Aman ✅ — works correctly now
This pattern is widely used in React and other component-based frameworks.
The Complete Mental Map
Here's every scenario in one place:
| How the function is called | this equals |
|---|---|
obj.fn() — called on an object |
That object |
fn() — called standalone |
window (or undefined in strict mode) |
fn.call(obj) / fn.apply(obj) |
Whatever you explicitly pass in |
Arrow function () => {} |
this inherited from the enclosing scope |
new MyClass() |
The newly created instance |
| DOM event listener | The element that triggered the event |
What to Learn Next
Mastering this opens the door to several powerful JavaScript concepts. Here's a logical path forward:
Prototypes and Prototype Chain —
thisis the foundation of how JavaScript's prototype system works. Understanding it will make inheritance click.ES6 Classes in Depth — Now that you understand
thisin classes, explore inheritance,super(), and private fields.Closures —
thisand closures are often confused. Learning both side-by-side will sharpen your understanding of scope and context.The Event Loop — Understand why
setTimeoutand async callbacks losethiscontext, and how the event loop drives JavaScript execution.
💬 Got Questions?
Drop a comment below! I'd love to hear about any this-related bug that stumped you, or help clarify any section that didn't fully click.
Here are topics coming up in future articles:
Closures Explained Simply: Why functions "remember" variables even after they've finished running — and how to use that power intentionally.
Prototypes Without the Confusion: A visual walkthrough of how JavaScript's prototype chain actually works under the hood.
async/awaitFrom Zero: Promises are great, butasync/awaitmakes asynchronous code feel synchronous — here's how to write it confidently.call(),apply(),bind()Deep Dive: Real-world use cases, patterns, and gotchas for all three methods — with exercises.
Found this helpful? Share it with someone who's been bitten by a this bug lately — you might just save them an hour of debugging.




