this is one of the most common sources of unexpected bugs in JavaScript. In most languages, this is simply a reference to the current object. In JavaScript it's different: the value of this is determined not by where a function is written, but by how it is called. The same function will get a different this depending on how it is invoked.
Four rules for determining this
1. Method call on an object. this is the object to the left of the dot.
const user = {
name: "Alice",
greet() {
console.log(this.name); // "Alice"
},
};
user.greet();
2. Plain function call. this is the global object (window in a browser) or undefined in strict mode.
function greet() {
console.log(this); // window or undefined
}
greet();
3. call, apply, bind. Explicit this assignment.
function greet(greeting) {
console.log(`${greeting}, ${this.name}`);
}
const user = { name: "Alice" };
greet.call(user, "Hello"); // "Hello, Alice"
greet.apply(user, ["Hello"]); // same, arguments as array
const bound = greet.bind(user);
bound("Hello"); // "Hello, Alice"
4. Constructor (new). this is the newly created object.
function User(name) {
this.name = name;
}
const alice = new User("Alice");
console.log(alice.name); // "Alice"
Strict mode: this without an object
In non-strict mode, a plain function call gives this === window. In strict mode ("use strict") it gives this === undefined. ES modules and class bodies always run in strict mode.
"use strict";
function greet() {
console.log(this); // undefined, not window
}
greet();
Arrow functions: lexical this
Arrow functions do not have their own this. They capture it from the surrounding lexical context — from the place where the arrow function is written, not from where it is called.
const counter = {
count: 0,
start() {
setInterval(() => {
this.count++; // this = counter, captured from start()
console.log(this.count);
}, 1000);
},
};
counter.start();
If you replace the arrow with a regular function, this inside setInterval becomes window (or undefined), and count is lost.
Losing this when passing a method as a callback
This is a classic trap. Passing a method as a callback "detaches" it from its object.
const user = {
name: "Alice",
greet() {
console.log(this.name);
},
};
setTimeout(user.greet, 100); // undefined — this is lost
document.querySelector("button").addEventListener("click", user.greet); // undefined
The method is passed as a bare function and called without an object on the left — rule 2 applies, not rule 1.
Ways to fix it:
// bind — creates a new function with a fixed this
setTimeout(user.greet.bind(user), 100);
// arrow wrapper — this is taken lexically
setTimeout(() => user.greet(), 100);
this in classes
In classes, methods follow the object method rule, but when passed as a callback — the same trap applies. The fix: arrow class fields.
class Button {
label = "Click me";
// Regular method — this is lost when passed as a callback
handleClick() {
console.log(this.label); // can be undefined
}
// Arrow field — this is permanently bound
handleClickArrow = () => {
console.log(this.label); // always "Click me"
};
}
this in React components
In class components, losing this in event handlers was a daily problem: you had to call this.handleClick = this.handleClick.bind(this) in the constructor, or use arrow class fields.
In functional components there is no this problem at all. Event handlers are written as closures — variables are taken from the function's scope, not via this.
function Counter() {
const [count, setCount] = React.useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}
This is one of the reasons functional components became the standard.
Traps: what does the code print
const obj = {
val: 42,
getVal() { return this.val; },
};
const fn = obj.getVal;
console.log(fn()); // undefined (this = window/undefined)
console.log(obj.getVal()); // 42
console.log(fn.call(obj)); // 42
const obj = {
val: 1,
outer() {
const inner = function () {
return this.val; // this is lost — regular function
};
return inner();
},
};
console.log(obj.outer()); // undefined — use an arrow instead of function to fix
In short
thisis determined by the way the function is called, not where it is declared.- Four rules: method (
obj.fn()) → the object; plain call →window/undefined;call/apply/bind→ the given object;new→ the new object. - In strict mode (modules, classes), a plain call without an object gives
undefined, notwindow. - Arrow functions have no
thisof their own — they take it lexically from the surrounding code. - Passing a method as a callback loses
this. Fix with.bind()or an arrow wrapper. - In functional React components
thisis not needed: handlers work as closures.
What to read next
- JavaScript essentials for TypeScript — closures and scope, the foundation for understanding this.
- Event loop: how it works and where it bites — setTimeout, callbacks, and execution order.
- DOM events: bubbling, capturing, delegation — where losing this most commonly happens in practice.