

No single concept in JavaScript causes more confusion than “this.” Unlike most languages, where the meaning of “this” (or “self”) is fixed once a method is defined, JavaScript decides what “this” refers to based entirely on how a function is called, not where it was written. That one fact explains almost every “this” bug you’ll ever encounter.
The Core Rule: How, Not Where
JavaScript looks at the call site — the exact expression used to invoke a function — to determine what “this” means inside it. The same function, called two different ways, can have two completely different values of “this.”
const user = {
name: 'Amara',
greet() {
console.log(`Hi, I'm ${this.name}`);
}
};
user.greet(); // "Hi, I'm Amara" — this is user, because it was called as user.greet()
const detachedGreet = user.greet;
detachedGreet(); // "Hi, I'm undefined" — this is no longer user, because the call site changedThat second call is the single most common source of “this” bugs: passing a method as a callback (to setTimeout, an event listener, an array method) detaches it from the object, and “this” stops pointing where you expect.
The Four Rules of “this” Binding
- Default binding: a plain function call (not attached to any object) gets this === undefined in strict mode, or the global object otherwise
- Implicit binding: calling a function as a method (obj.method()) sets this to the object left of the dot
- Explicit binding: call(), apply(), or bind() let you set this manually, overriding the other rules
- new binding: calling a function with new creates a brand-new object and sets this to it
function whoAmI() { console.log(this?.name); }
whoAmI(); // undefined (default binding, no object)
const obj = { name: 'Chidi', whoAmI };
obj.whoAmI(); // 'Chidi' (implicit binding)
whoAmI.call({ name: 'Explicit' }); // 'Explicit' (explicit binding)
function Person(name) { this.name = name; }
const p = new Person('New'); // this is the newly created object
Arrow Functions Break the Rules on Purpose
Arrow functions don’t have their own “this” at all. Instead, they capture “this” from the surrounding scope at the moment they’re defined, and that binding never changes no matter how the arrow function is later called. This is exactly why arrow functions became the standard fix for the classic callback problem:
const timer = {
seconds: 0,
start() {
// Regular function: 'this' inside setTimeout's callback would NOT be timer
setTimeout(function () {
this.seconds++; // this is undefined or the global object — bug
}, 1000);
// Arrow function: inherits 'this' from start(), which is timer — correct
setTimeout(() => {
this.seconds++; // this is timer, as expected
console.log(this.seconds);
}, 1000);
}
};Fixing Lost Context: bind, call, apply
When you do need to pass a regular function as a callback and preserve its “this,” bind() creates a new function permanently locked to the object you specify.
class Counter {
constructor() { this.count = 0; }
increment() { this.count++; console.log(this.count); }
}
const counter = new Counter();
const button = { onClick: null };
// Without bind, 'this' inside increment would not be counter when called as a callback
button.onClick = counter.increment.bind(counter);
button.onClick(); // 1, correctly bound to counterFrequently Asked Questions
Why does “this” become undefined when I pass a method as a callback?
Because “this” is determined by how a function is called, not where it’s defined. Passing method as a value and calling it later (obj.method vs. just method()) strips away the object context that made “this” work in the first place.
Should I always use arrow functions to avoid “this” bugs?
Not always — arrow functions are ideal for callbacks where you want to inherit the surrounding “this,” but object methods generally still need regular function syntax so “this” correctly refers to the object the method is called on.
What’s the difference between call(), apply(), and bind()?
call() and apply() invoke a function immediately with a specified “this” (call takes arguments individually, apply takes them as an array); bind() doesn’t invoke the function at all, it returns a new function permanently bound to that “this” for later use.
Want more JavaScript deep dives like this? Explore more Web Development articles.