Back to Blog
Advanced2025-01-06

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