Ask 10 JavaScript developers what this means, and you'll get 10 different explanations. It's the source of countless bugs, especially for developers coming from other languages where this (or self) is straightforward.
Here's the uncomfortable truth: In JavaScript, this isn't determined by where a function is defined — it's determined by how the function is called.
This single fact explains 90% of the confusion.
The Golden Rule
The value of this is determined by the call site (how the function is invoked), not where the function is written. There are four binding rules, and arrow functions break all of them by lexically binding this to their surrounding scope.
Let's break down each rule systematically.
Part 1: The Four Binding Rules
Rule 1: Default Binding (Function Invocation)
When a function is called standalone (not as a method, not with new, not with call/apply/bind), this defaults to the global object (or undefined in strict mode).
function showThis() {
console.log(this);
}
showThis(); // In browser: Window, in Node: global, in strict mode: undefined
In strict mode:
'use strict';
function showThis() {
console.log(this);
}
showThis(); // undefined
Key Point: This is the "fallback" rule when no other rule applies.
Rule 2: Implicit Binding (Method Invocation)
When a function is called as a method of an object, this refers to the object before the dot.
const person = {
name: 'Alice',
greet: function() {
console.log(`Hello, I'm ${this.name}`);
}
};
person.greet(); // "Hello, I'm Alice"
The rule: Look at the call site. What's to the left of the dot? That's this.
Common Mistake: Losing Implicit Binding
const person = {
name: 'Alice',
greet: function() {
console.log(`Hello, I'm ${this.name}`);
}
};
const greet = person.greet; // Extract the function
greet(); // "Hello, I'm undefined" (or error in strict mode)
Why? When you call greet() (not person.greet()), there's no object before the dot, so default binding applies, not implicit binding.
This also happens with callbacks:
setTimeout(person.greet, 1000); // Loses 'this'
Fix:
setTimeout(() => person.greet(), 1000); // Call it as a method
// or
setTimeout(person.greet.bind(person), 1000); // Explicitly bind
Rule 3: Explicit Binding (call, apply, bind)
You can explicitly set this using call(), apply(), or bind().
call() and apply()
function greet(greeting, punctuation) {
console.log(`${greeting}, I'm ${this.name}${punctuation}`);
}
const person = { name: 'Alice' };
greet.call(person, 'Hello', '!'); // "Hello, I'm Alice!"
greet.apply(person, ['Hi', '.']); // "Hi, I'm Alice."
Difference:
-
call(thisArg, arg1, arg2, ...)— arguments passed individually -
apply(thisArg, [arg1, arg2, ...])— arguments passed as array
bind()
function greet() {
console.log(`Hello, I'm ${this.name}`);
}
const person = { name: 'Alice' };
const boundGreet = greet.bind(person); // Returns NEW function with 'this' locked
boundGreet(); // "Hello, I'm Alice"
setTimeout(boundGreet, 1000); // 'this' is still 'person'
Key Point: bind() returns a new function with this permanently set. Useful for callbacks.
Rule 4: new Binding (Constructor Invocation)
When you call a function with new, JavaScript:
- Creates a new empty object
- Sets the object's
[[Prototype]]to the function'sprototype - Binds
thisto the new object - Returns the object (unless the function explicitly returns a different object)
function Person(name) {
this.name = name; // 'this' refers to the new object
console.log(this);
}
const alice = new Person('Alice');
// Logs: Person { name: 'Alice' }
console.log(alice.name); // "Alice"
What new does behind the scenes:
function Person(name) {
// const this = Object.create(Person.prototype); (implicit)
this.name = name;
// return this; (implicit)
}
What if a constructor returns an object?
function Person(name) {
this.name = name;
return { different: true }; // Explicit return
}
const alice = new Person('Alice');
console.log(alice); // { different: true } (not Person instance!)
Rule: If a constructor returns an object, that object is returned instead of this. If it returns a primitive, this is returned.
Part 2: Arrow Functions (Breaking the Rules)
Arrow functions don't have their own this. Instead, they lexically inherit this from their surrounding scope.
const person = {
name: 'Alice',
greet: function() {
const inner = () => {
console.log(this.name); // 'this' is inherited from greet()
};
inner();
}
};
person.greet(); // "Alice"
Key Point: The arrow function doesn't care how it's called — it captures this from where it's defined.
Arrow Functions vs Regular Functions
const person = {
name: 'Alice',
regularGreet: function() {
console.log(this.name);
},
arrowGreet: () => {
console.log(this.name); // 'this' is NOT 'person'!
}
};
person.regularGreet(); // "Alice" (implicit binding)
person.arrowGreet(); // undefined (lexical binding to outer scope, probably global)
Why? The arrow function was defined in the global scope (outside any function), so its this is the global object (or undefined in modules).
When Arrow Functions are Perfect
Avoiding this confusion in callbacks:
const person = {
name: 'Alice',
hobbies: ['reading', 'coding'],
showHobbies: function() {
this.hobbies.forEach(hobby => {
console.log(`${this.name} likes ${hobby}`); // 'this' is 'person'
});
}
};
person.showHobbies();
// "Alice likes reading"
// "Alice likes coding"
With a regular function (old way):
showHobbies: function() {
const self = this; // Save reference
this.hobbies.forEach(function(hobby) {
console.log(`${self.name} likes ${hobby}`); // Use 'self'
});
}
Arrow functions eliminate the need for self = this hacks.
Part 3: Binding Precedence
When multiple rules apply, what wins?
Precedence (highest to lowest):
-
Arrow functions (lexical
this, ignores all other rules) -
newbinding (newalways wins) -
Explicit binding (
call,apply,bind) - Implicit binding (method call)
- Default binding (standalone function call)
Example: new vs bind
function Person(name) {
this.name = name;
}
const boundPerson = Person.bind({ name: 'Bound' });
const instance = new boundPerson('Alice');
console.log(instance.name); // "Alice" (new wins over bind)
Example: Explicit vs Implicit
const person1 = { name: 'Alice' };
const person2 = { name: 'Bob' };
function greet() {
console.log(this.name);
}
person1.greet = greet;
person1.greet.call(person2); // "Bob" (explicit wins over implicit)
Part 4: this in React
Understanding this is critical for React class components (and understanding legacy code).
1. Class Components and Method Binding
The problem:
class Button extends React.Component {
constructor(props) {
super(props);
this.state = { clicked: false };
}
handleClick() {
this.setState({ clicked: true }); // 'this' is undefined!
}
render() {
return <button onClick={this.handleClick}>Click me</button>;
}
}
Why does this fail?
- When React calls the event handler, it's a standalone function call (default binding)
- In strict mode (React uses strict mode),
thisisundefined
Solutions
Solution 1: Arrow function property (modern)
class Button extends React.Component {
handleClick = () => { // Arrow function
this.setState({ clicked: true });
}
render() {
return <button onClick={this.handleClick}>Click me</button>;
}
}
How it works: Arrow functions lexically bind this to the class instance.
Trade-off: Creates a new function per instance (not on prototype), slightly more memory.
Solution 2: Bind in constructor
class Button extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this); // Bind once
}
handleClick() {
this.setState({ clicked: true });
}
render() {
return <button onClick={this.handleClick}>Click me</button>;
}
}
How it works: bind() creates a new function with this locked to the instance.
Solution 3: Arrow function in JSX (not recommended)
class Button extends React.Component {
handleClick() {
this.setState({ clicked: true });
}
render() {
return <button onClick={() => this.handleClick()}>Click me</button>;
}
}
Why not recommended: Creates a new function on every render, which can cause unnecessary re-renders in child components.
2. Functional Components (No this Problems!)
Modern React with hooks eliminates this entirely:
function Button() {
const [clicked, setClicked] = useState(false);
const handleClick = () => {
setClicked(true); // No 'this' needed!
};
return <button onClick={handleClick}>Click me</button>;
}
Why this is cleaner:
- No
thisbinding issues - No need for constructors
- Closures handle state access (see Closures article!)
3. Passing this Context to Children
In class components:
class Parent extends React.Component {
state = { count: 0 };
increment = () => {
this.setState({ count: this.state.count + 1 });
}
render() {
return <Child onIncrement={this.increment} />;
}
}
function Child({ onIncrement }) {
return <button onClick={onIncrement}>Increment</button>;
}
The increment arrow function ensures this is always the Parent instance, even when called from Child.
4. Event Handlers and Synthetic Events
React's event system preserves this in interesting ways:
class Form extends React.Component {
state = { value: '' };
handleChange(event) { // Regular method
this.setState({ value: event.target.value }); // 'this' works!
}
render() {
return (
<input
value={this.state.value}
onChange={this.handleChange.bind(this)} // Must bind!
/>
);
}
}
Why does this work differently than other callbacks?
- It doesn't! You still need to bind
- The difference is React automatically binds some lifecycle methods (
render,constructor)
Part 5: Common this Gotchas
Gotcha 1: Nested Function Calls
const obj = {
value: 42,
getValue: function() {
function inner() {
return this.value; // 'this' is undefined (strict mode)
}
return inner();
}
};
console.log(obj.getValue()); // undefined or error
Fix:
getValue: function() {
const inner = () => this.value; // Arrow function
return inner();
}
Gotcha 2: this in Object Literals
const obj = {
value: 42,
getValue: function() {
return this.value; // Works
},
getValueArrow: () => {
return this.value; // 'this' is NOT obj!
}
};
console.log(obj.getValue()); // 42
console.log(obj.getValueArrow()); // undefined
Why? The arrow function is defined in the global scope, so its this is global (or undefined in modules).
Gotcha 3: this in Async Functions
const obj = {
value: 42,
async getValue() {
return this.value; // 'this' is preserved
}
};
obj.getValue().then(console.log); // 42
Good news: async/await doesn't change this behavior — it follows the same binding rules.
Gotcha 4: this in Classes with Callbacks
class Counter {
constructor() {
this.count = 0;
}
increment() {
this.count++;
}
start() {
setInterval(this.increment, 1000); // 'this' is lost!
}
}
const counter = new Counter();
counter.start(); // Error: Cannot read 'count' of undefined
Fix:
start() {
setInterval(() => this.increment(), 1000); // Arrow function
// or
setInterval(this.increment.bind(this), 1000); // Bind
}
Quick Reference Cheat Sheet
| Binding Type | Call Site Example |
this Value |
|---|---|---|
| Default | func() |
undefined (strict mode) or global |
| Implicit | obj.func() |
obj |
| Explicit | func.call(obj) |
obj |
new |
new Func() |
New instance |
| Arrow | () => {} |
Lexical (outer scope) |
Key Takeaways
this is determined by how a function is called, not where it's defined
Four binding rules: default, implicit, explicit, new (in order of precedence)
Arrow functions lexically bind this from their surrounding scope
Losing this in callbacks is the most common bug — use arrow functions or bind()
In React class components, bind methods in the constructor or use arrow functions
Functional components with hooks avoid this entirely (one reason they're preferred)
bind() creates a new function; call() and apply() invoke immediately
Interview Tip
When asked about this, explain it systematically:
- "The value of
thisdepends on how a function is called, not where it's defined" -
Walk through the four rules: default, implicit, explicit,
new -
Mention arrow functions: "Arrow functions don't have their own
this— they inherit it lexically" - React example: "In class components, event handlers need binding because they're called as standalone functions, losing the implicit binding to the class instance"
-
Functional components: "Modern React avoids
thisentirely with hooks and closures"
Now go forth and never lose this again!
Top comments (0)