Understanding the `this` Keyword in JavaScript: What It Is and How Calling Context Controls It

Understanding the this Keyword in JavaScript: What It Is and How Calling Context Controls It
TL;DR: this in JavaScript refers to the object that invoked the function — not where the function was defined. Its value is determined at call time, not at write time. Once you internalize that mental model, nearly every this confusion disappears.
Audience: This post targets developers who know basic JavaScript but get tripped up by
thisbehaving unexpectedly in callbacks, event handlers, or object methods.
Problem
You write a method on an object, it works fine when called directly — then you pass it as a callback and it breaks. this is suddenly undefined or pointing at window. You've seen this before:
const user = {
name: "Riya",
greet() {
console.log(`Hello, I'm ${this.name}`);
}
};
const greetFn = user.greet;
greetFn(); // Hello, I'm undefined
The function didn't change. The caller did. That's the entire story of this.
Solution: The Core Mental Model
this = the object to the left of the dot when the function is called.
If there's no object to the left of the dot, this falls back to the global object (window in browsers, global in Node.js) — or undefined in strict mode.
This is called implicit binding and it's the default rule.
Caller → Function
↑
this
When user.greet() is called, user is to the left of the dot. So this = user.
When greetFn() is called with no object, there's nothing to the left. So this = undefined (strict) or window (non-strict).
Step 1: this in the Global Context
In a browser, code running at the top level has this pointing to window.
// Run this in a browser console (non-strict mode)
console.log(this === window); // true
this.appName = "MyApp";
console.log(window.appName); // "MyApp"
In Node.js, the top-level this inside a module is an empty object {}, not global. This surprises people:
// In a Node.js module file
console.log(this); // {}
console.log(this === global); // false
// But inside a regular function at top level (non-strict):
function showContext() {
console.log(this === global);
}
showContext(); // true
Why: Node.js wraps each file in a module wrapper function. The top-level this is the module's exports object, not global.
Step 2: this Inside Object Methods
This is where this is most intuitive. The object calling the method becomes this.
const account = {
owner: "Arjun",
balance: 5000,
showBalance() {
console.log(`${this.owner} has ₹${this.balance}`);
}
};
account.showBalance();
// Output: Arjun has ₹5000
account is to the left of .showBalance(), so this = account.
Now a nested object example — this is where developers get confused:
const bank = {
name: "NeoBank",
account: {
owner: "Priya",
showOwner() {
console.log(this.owner); // "Priya" ✅
console.log(this.name); // undefined ❌ (this is account, not bank)
}
}
};
bank.account.showOwner();
// Output:
// Priya
// undefined
Why: The immediate caller is bank.account, not bank. this only goes one level up — to the direct caller.
Step 3: this Inside Regular Functions
A standalone function call has no object context. In non-strict mode, this defaults to the global object. In strict mode, it's undefined.
"use strict";
function identify() {
console.log(this);
}
identify(); // undefined (strict mode)
// Without strict mode
function identify() {
console.log(this === window); // true (in browser)
}
identify();
This becomes a real problem inside object methods when you use a helper function:
"use strict";
const cart = {
items: ["apple", "mango"],
printItems() {
this.items.forEach(function(item) {
console.log(this); // undefined — regular function, lost context
});
}
};
cart.printItems();
The forEach callback is a regular function. It's not called as cart.something(). There's no object to the left of the dot — so this is undefined in strict mode.
Step 4: How Calling Context Changes this
The same function can have different values of this depending on how it's called.
const order = {
id: "ORD-001",
printId() {
console.log(`Order: ${this.id}`);
}
};
const delivery = {
id: "DEL-999"
};
// Normal call — this = order
order.printId(); // Order: ORD-001
// Borrow the method — this = delivery
delivery.printId = order.printId;
delivery.printId(); // Order: DEL-999
// Detach — this = undefined (strict) or window
const standalone = order.printId;
standalone(); // Order: undefined
Same function. Three different results. Caller determines this.
Step 5: Fixing Lost this — Arrow Functions
Arrow functions do not have their own this. They inherit this from the surrounding lexical scope at the time they are defined.
"use strict";
const cart = {
items: ["apple", "mango"],
printItems() {
// Arrow function captures `this` from printItems — which is cart
this.items.forEach((item) => {
console.log(`${this.items.indexOf(item) + 1}. ${item}`);
});
}
};
cart.printItems();
// Output:
// 1. apple
// 2. mango
This works because the arrow function doesn't create its own this. It uses the this from printItems, which is cart.
Important: Never use arrow functions as object methods if you need this to refer to the object.
const profile = {
name: "Dev",
// ❌ Arrow function as method — this is NOT profile
greet: () => {
console.log(`Hello, ${this.name}`);
}
};
profile.greet(); // Hello, undefined
Why? The arrow function was defined in the outer scope (likely global or module scope), so this is that outer scope — not profile.
Step 6: Explicit Binding with call, apply, and bind
You can manually set this using these three methods.
function introduce(role, city) {
console.log(`I'm ${this.name}, a ${role} from ${city}`);
}
const dev = { name: "Sneha" };
// call — invoke immediately, args passed one by one
introduce.call(dev, "frontend engineer", "Pune");
// I'm Sneha, a frontend engineer from Pune
// apply — invoke immediately, args passed as array
introduce.apply(dev, ["backend engineer", "Bengaluru"]);
// I'm Sneha, a backend engineer from Bengaluru
// bind — returns a new function with this permanently set
const boundIntroduce = introduce.bind(dev, "fullstack engineer");
boundIntroduce("Mumbai");
// I'm Sneha, a fullstack engineer from Mumbai
bind is especially useful when passing methods as callbacks:
const timer = {
label: "Request Timer",
start() {
console.log(`${this.label} started`);
}
};
// Without bind — this is lost
setTimeout(timer.start, 1000); // undefined started
// With bind — this is preserved
setTimeout(timer.start.bind(timer), 1000); // Request Timer started
Results
After applying these rules, here's a quick reference:
| Call Pattern | this Value |
obj.method() | obj |
method() (no object) | undefined (strict) / window (non-strict) |
| Arrow function inside method | Inherited from surrounding scope |
| Arrow function as method | Outer scope (window / undefined) |
fn.call(obj) | obj |
fn.apply(obj) | obj |
fn.bind(obj)() | obj |
Trade-offs
Arrow functions aren't always the fix. They solve the callback problem but break method definitions on objects. Choosing between arrow and regular functions requires knowing whether you want this to be dynamic (regular) or lexically inherited (arrow).
bind creates a new function every time it's called. Calling fn.bind(obj) inside render() or a loop creates a new function reference on each call — that has memory implications in hot paths.
this is invisible in the function signature. Unlike parameters, the this binding isn't visible when reading a function definition. You have to trace the call site, which makes codebases harder to reason about at scale. This is one reason many teams prefer passing context explicitly as a parameter over relying on this.
Conclusion
this is determined by who calls the function, not where it's defined. That single sentence resolves most confusion. When you see unexpected this behavior:
- Find the call site
- Check what's to the left of the dot
- If nothing is there, apply the fallback rules (global or undefined)
- If using a callback, check whether it's an arrow function or a regular one
Next step: Practice by predicting this before running code. Write a function, call it three different ways (direct, detached, via call), and verify your predictions against the output.



