A developer-friendly guide to understanding how this
actually works in JavaScript — with clear rules, real examples, and gotchas you’ll meet in projects.

this
actually works in JavaScript — with clear rules, real examples, and gotchas you’ll meet in projects.Introduction
Every JavaScript developer has hit the dreaded this
confusion:
- Why is
this
undefined
inside a callback? - Why does it work in a method but not when you assign that method to a variable?
- Why do arrow functions magically “fix” things?
The keyword this
is one of the most misunderstood parts of JavaScript. Unlike variables, it’s not resolved lexically. Instead, this
is determined at runtime by how a function is called.
In this post, we’ll break down every scenario where this
behaves differently. You’ll walk away with a practical, copy-paste mental model you can apply in debugging and everyday coding.
1. Quick Definition
this
is a special binding automatically set when a function is executed.
- In strict mode, if no explicit binding is found,
this
isundefined
. - In non-strict mode, it defaults to the global object (
window
in browsers,global
in Node).
👉 Unlike variables, this
does not follow the scope chain. It’s set at the call site.
2. Global Context
Non-Strict Mode
console.log(this); // window (in browsers)
Strict Mode
"use strict";
console.log(this); // undefined
👉 Rule: In the global context, this
is the global object unless strict mode is enabled.
3. Inside a Function
Non-Strict Mode
function demo() {
console.log(this);
}
demo(); // window (or global in Node)
Strict Mode
"use strict";
function demo() {
console.log(this);
}
demo(); // undefined
👉 Rule: Plain function calls set this
to global (sloppy mode) or undefined
(strict mode).
4. Inside an Object Method
const user = {
name: "Alice",
greet() {
console.log(this.name);
}
};
user.greet(); // "Alice"
👉 Rule: When a function is called as a property of an object, this
is that object.
5. Detached Method (The Gotcha)
const user = {
name: "Alice",
greet() {
console.log(this.name);
}
};
const fn = user.greet;
fn(); // undefined (strict) or window/global (sloppy)
👉 Rule: Assigning a method to a variable loses the object context → this
is undefined/global.
6. Explicit Binding: call, apply, bind
You can override this
using built-in methods:
function greet() {
console.log(this.name);
}
const user = { name: "Bob" };
greet.call(user); // "Bob"
greet.apply(user); // "Bob"
const bound = greet.bind(user);
bound(); // "Bob"
call
→ pass arguments individuallyapply
→ pass arguments as arraybind
→ returns a permanently bound function
👉 Rule: Use explicit binding to control this
.
7. Constructors (new
Keyword)
When you call a function with new
, three things happen:
- A new empty object is created.
this
inside the function is bound to that object.- The object is returned (unless you return another object manually).
function User(name) {
this.name = name;
}
const u = new User("Charlie");
console.log(u.name); // "Charlie"
👉 Rule: new
binds this
to the newly created object.
8. Arrow Functions (Lexical this
)
Arrow functions don’t have their own this
. Instead, they capture this
from the surrounding lexical scope.
const team = {
name: "Core",
regular: function() {
setTimeout(function() {
console.log(this.name); // undefined
}, 0);
},
arrow: function() {
setTimeout(() => {
console.log(this.name); // "Core"
}, 0);
}
};
team.regular();
team.arrow();
👉 Rule: Arrow functions are the go-to fix for callbacks needing the outer this
.
9. Classes
Classes use the same rules as constructor functions. Methods are placed on the prototype and get this
from their call site.
class User {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hi, ${this.name}`);
}
}
const u = new User("Dana");
u.greet(); // "Hi, Dana"
Gotcha: Passing
u.greet
as a callback loses context → bind or use arrow functions.
10. this
in setTimeout
/ setInterval
In browsers, setTimeout
and setInterval
default this
to the global object (or undefined in strict mode).
setTimeout(function() {
console.log(this); // window (sloppy) / undefined (strict)
}, 0);
Use arrow functions or bind
:
setTimeout(() => console.log(this), 0);
11. this
in ES Modules vs Scripts
- In scripts, top-level
this
is the global object (non-strict). - In ES modules, top-level
this
is alwaysundefined
.
// script.js
console.log(this); // window
// module.mjs
console.log(this); // undefined
👉 Rule: Don’t rely on top-level this
in modern module code.
12. Event Handlers
By default, this
inside event handlers refers to the DOM element that triggered the event.
document.querySelector("button")
.addEventListener("click", function() {
console.log(this); // <button>
});
But arrow functions capture lexical this
:
document.querySelector("button")
.addEventListener("click", () => {
console.log(this); // window/undefined depending on strictness
});
👉 Rule: Use function expressions if you need the element as this
. Use arrows if you want outer context.
13. this
with Object.assign or Spread
When methods are copied between objects, this
changes depending on call site:
const obj1 = {
name: "obj1",
say() { console.log(this.name); }
};
const obj2 = { ...obj1, name: "obj2" };
obj2.say(); // "obj2"
👉 Rule: this
is dynamic — tied to the caller, not the object where the function was defined.
14. this
with with
and eval
(Avoid)
with
changes lexical scope dynamically (strict mode forbids it).eval
can inject variables, but doesn’t affectthis
.
Both make reasoning about this
harder → avoid them.
15. Special Case: Getters/Setters
Inside getters/setters, this
refers to the object the property belongs to.
const person = {
first: "Eve",
last: "Adams",
get full() {
return `${this.first} ${this.last}`;
}
};
console.log(person.full); // "Eve Adams"
16. Decision Table

17. Common Gotchas & Fixes
Gotcha: Losing Context in Callbacks
const obj = {
name: "Ava",
greet() { console.log(this.name); }
};
setTimeout(obj.greet, 0); // undefined
Fix: Bind or wrap.
setTimeout(obj.greet.bind(obj), 0);
setTimeout(() => obj.greet(), 0);
Gotcha: Arrow Functions in Object Methods
const obj = {
name: "Leo",
greet: () => console.log(this.name)
};
obj.greet(); // undefined (arrow captures outer `this`)
Fix: Use method shorthand or function expression.
Gotcha: React Class Components
class Counter extends React.Component {
constructor() {
super();
this.count = 0;
}
handleClick() {
console.log(this.count); // ❌ undefined if not bound
}
render() {
return <button onClick={this.handleClick}>+</button>;
}
}
Fix by binding:
<button onClick={this.handleClick.bind(this)}>+</button>
or by using class fields with arrows:
handleClick = () => console.log(this.count);
Conclusion
The this
keyword isn’t random or magical. It follows clear rules based on how a function is called:
- Default global/undefined in functions.
- Object before the dot in methods.
- Explicitly controlled with
call
,apply
,bind
. - Newly created object in constructors.
- Lexical inheritance in arrow functions.
- Special cases for modules, classes, and event handlers.
Pro tip: When debugging, ask: How was this function called? With new
? As a method? As a plain function? Bound? Arrow? That question usually reveals the correct this
.
Call to Action
What’s the weirdest this
bug you’ve run into in production?
💬 Share your story in the comments.
🔖 Bookmark this as your this
quick reference.
👩💻 Share it with a teammate who still thinks this
follows lexical scope.
Leave a Reply