Understanding "this" in JavaScript: The Complete Guide
Master the confusing "this" keyword. Learn the 4 rules that determine what "this" refers to in any situation.
Why is "this" So Confusing?
In most languages, this always refers to the current object instance. In JavaScript, this is determined by how a function is called, not where it's defined. This single difference causes endless confusion.
``javascript
const user = {
name: 'Alice',
greet() {
console.log('Hello, ' + this.name);
}
};
user.greet(); // "Hello, Alice" - this is user
const greet = user.greet;
greet(); // "Hello, undefined" - this is NOT user!
`
Same function, different this. Let's understand why.
The 4 Rules of "this"
JavaScript determines this using these rules, in order of priority:
Rule 1: new Binding (Highest Priority)
When using new, this refers to the newly created object:
`javascript
function User(name) {
this.name = name;
// this = newly created object
}
const alice = new User('Alice');
console.log(alice.name); // 'Alice'
`
Rule 2: Explicit Binding (call, apply, bind)
You can explicitly set this using call, apply, or bind:
`javascript
function greet() {
console.log('Hello, ' + this.name);
}
const user1 = { name: 'Alice' }; const user2 = { name: 'Bob' };
greet.call(user1); // "Hello, Alice" greet.apply(user2); // "Hello, Bob"
const boundGreet = greet.bind(user1);
boundGreet(); // "Hello, Alice"
`
call vs apply vs bind:
- call: Invokes immediately, args passed individually
- apply: Invokes immediately, args passed as array
- bind: Returns new function with this permanently bound
`javascript
function introduce(greeting, punctuation) {
console.log(greeting + ', I am ' + this.name + punctuation);
}
const user = { name: 'Alice' };
introduce.call(user, 'Hi', '!'); // "Hi, I am Alice!" introduce.apply(user, ['Hi', '!']); // "Hi, I am Alice!"
const boundIntro = introduce.bind(user, 'Hello');
boundIntro('.'); // "Hello, I am Alice."
`
Rule 3: Implicit Binding
When calling a method on an object, this is that object:
`javascript
const user = {
name: 'Alice',
greet() {
console.log('Hello, ' + this.name);
}
};
user.greet(); // "Hello, Alice" - this = user
`
Beware of losing implicit binding:
`javascript
const user = {
name: 'Alice',
greet() {
console.log('Hello, ' + this.name);
}
};
// Lost binding! const greetFunc = user.greet; greetFunc(); // "Hello, undefined"
// Also lost in callbacks!
setTimeout(user.greet, 100); // "Hello, undefined"
`
Rule 4: Default Binding (Lowest Priority)
If no other rule applies, this is:
- undefined in strict mode
- The global object (window/global) in non-strict mode
`javascript
function showThis() {
console.log(this);
}
showThis(); // window (or undefined in strict mode)
`
Arrow Functions: The Exception
Arrow functions don't have their own this. They inherit this from their enclosing scope:
`javascript
const user = {
name: 'Alice',
greet: function() {
// Regular function: this = user
setTimeout(() => {
// Arrow function: this = inherited from greet = user
console.log('Hello, ' + this.name);
}, 100);
}
};
user.greet(); // "Hello, Alice" (works!)
// Compare with regular function: const user2 = { name: 'Bob', greet: function() { setTimeout(function() { // Regular function: this = window/undefined console.log('Hello, ' + this.name); }, 100); } };
user2.greet(); // "Hello, undefined" (broken!)
`
When to use arrow functions:
- Callbacks where you want to preserve this
- Array methods (map, filter, forEach)
- Event handlers in classes
When NOT to use arrow functions:
- Object methods (you usually want this to be the object)
- Constructors (arrow functions can't be used with new)
Common "this" Patterns and Fixes
Pattern 1: Callback this Loss
`javascript
class Counter {
constructor() {
this.count = 0;
}
increment() { this.count++; } }
const counter = new Counter();
// BROKEN document.getElementById('btn').addEventListener('click', counter.increment); // this = button element, not counter!
// FIX 1: Arrow function document.getElementById('btn').addEventListener('click', () => counter.increment());
// FIX 2: bind document.getElementById('btn').addEventListener('click', counter.increment.bind(counter));
// FIX 3: Arrow function as class property
class Counter2 {
count = 0;
increment = () => {
this.count++; // this is always the instance
}
}
`
Pattern 2: Nested Functions
`javascript
const user = {
name: 'Alice',
friends: ['Bob', 'Charlie'],
// BROKEN showFriendsBroken() { this.friends.forEach(function(friend) { console.log(this.name + ' knows ' + friend); // this = undefined! }); },
// FIX: Arrow function
showFriends() {
this.friends.forEach((friend) => {
console.log(this.name + ' knows ' + friend); // this = user
});
}
};
`
Quick Reference: What is "this"?
| Call Style | this Value |
|------------|------------|
| new Func() | New object |
| func.call(obj) | obj |
| func.apply(obj) | obj |
| func.bind(obj)() | obj |
| obj.func() | obj |
| func() | global/undefined |
| Arrow function | Inherited from outer scope |
Summary
1. this is determined by how a function is called
2. Priority: new > explicit > implicit > default
3. Arrow functions inherit this from their surrounding scope
4. Use bind() or arrow functions to preserve this in callbacks
5. In strict mode, default this is undefined`, not global