Home JavaScript The this Keyword in JavaScript Explained — Context, Binding and Real-World Patterns

The this Keyword in JavaScript Explained — Context, Binding and Real-World Patterns

In Plain English 🔥
Imagine you work at a coffee shop. When your manager says 'clean YOUR station', the word 'your' means something different depending on who's being spoken to — barista, cashier, or manager. The word itself never changes, but its meaning depends entirely on who's in the room. That's exactly what 'this' does in JavaScript — it's a pronoun that refers to whoever is in charge of the current execution context. It's not a fixed thing; it shifts based on who's calling the function.
⚡ Quick Answer
Imagine you work at a coffee shop. When your manager says 'clean YOUR station', the word 'your' means something different depending on who's being spoken to — barista, cashier, or manager. The word itself never changes, but its meaning depends entirely on who's in the room. That's exactly what 'this' does in JavaScript — it's a pronoun that refers to whoever is in charge of the current execution context. It's not a fixed thing; it shifts based on who's calling the function.

If you've ever seen a bug where a callback suddenly can't find a method it definitely should have access to, or where 'this.name' returns undefined inside a perfectly normal-looking function — you've already met the chaos that this can cause. It's one of the most misunderstood features in the entire language, and it trips up developers at every experience level. Understanding it isn't just about avoiding bugs; it's about writing JavaScript that actually does what you think it does.

The problem this solves is elegant: it lets a single function behave correctly when used in different object contexts, without hardcoding which object it belongs to. Instead of writing a separate sayHello function for every object, you write one, and this fills in the right owner at runtime. Without it, object-oriented patterns in JavaScript would require far more boilerplate and duplication.

By the end of this article, you'll be able to predict exactly what this refers to in any situation — inside regular functions, arrow functions, class methods, callbacks, and event handlers. You'll know how to lock this to the right value using bind, call, and apply, and you'll stop losing hours to the most common this-related bugs that catch even experienced developers off guard.

What 'this' Actually Refers To — and Why It Isn't Fixed

Most people expect this to mean 'the object this function is defined inside'. That's the trap. In JavaScript, this doesn't care where a function is written — it cares how the function is called. That distinction is everything.

There are four main rules that determine what this points to at any given moment, and they're applied in priority order. The simplest is the default binding: when you call a plain function with no object in front of it, this is either the global object (window in a browser, global in Node.js) or undefined in strict mode. This is the rule that catches most beginners off guard.

The moment you call a function as a method of an object — meaning there's a dot before the function name at the call site — this becomes that object. Notice the phrase 'at the call site', not 'at the definition site'. You can define a function outside an object, assign it as a property, and this will still point to that object when it's called through the dot. That's implicit binding, and it's the most common form you'll use day to day.

The key mental model: think of this as being assigned at the moment the function is invoked, not when it's written.

thisBasicBinding.js · JAVASCRIPT
123456789101112131415161718192021222324252627282930
// ── Default Binding ──────────────────────────────────────────
// Called as a plain function — no object in front of it.
function describeWeather() {
  // In non-strict mode, 'this' here is the global object.
  // In strict mode ('use strict'), 'this' is undefined.
  console.log('Weather reporter this:', this === globalThis);
}

describeWeather(); // true (non-strict, browser or Node)

// ── Implicit Binding ─────────────────────────────────────────
// 'this' is determined by what's to the LEFT of the dot at call time.
const coffeeMachine = {
  brand: 'BrewMaster 3000',
  describe() {
    // 'this' is coffeeMachine because we called coffeeMachine.describe()
    console.log(`Machine brand: ${this.brand}`);
  }
};

coffeeMachine.describe(); // Machine brand: BrewMaster 3000

// ── The Trap: Losing Implicit Binding ────────────────────────
// We pull the method out and store it as a plain variable.
const standAloneDescribe = coffeeMachine.describe;

// Now there's NO dot — default binding kicks in.
// In strict mode this is undefined → TypeError crash.
// In non-strict mode this is globalThis → this.brand is undefined.
standAloneDescribe(); // Machine brand: undefined
▶ Output
Weather reporter this: true
Machine brand: BrewMaster 3000
Machine brand: undefined
⚠️
Watch Out: The Call Site, Not the Definition SiteJavaScript assigns 'this' when the function is called, not when it's defined. If you store a method in a variable and call it without the dot, you lose the object context instantly — this becomes global or undefined. This is the #1 source of this-related bugs in real codebases.

Taking Control — bind, call and apply Explained with Real Use Cases

Because this shifts based on how a function is called, JavaScript gives you three tools to take explicit control of it: call, apply, and bind. These let you say 'I don't care what the call site looks like — this is the object I want this to point to'.

call and apply are the immediate versions. They invoke the function right now with a specific this value. The only difference is how you pass arguments: call takes them comma-separated, apply takes them as an array. A useful memory trick — Apply starts with 'A' for Array.

bind is different. It doesn't call the function — it returns a brand-new function permanently locked to the this value you provide. No matter how many times you call that new function, or what object you attach it to later, this will never change. This is exactly what you need for callbacks and event handlers where you don't control how the function will eventually be called.

These aren't just academic tools. bind is the backbone of how React class components historically handled event handlers. Call is invaluable when borrowing array methods for array-like objects like arguments or NodeList.

thisExplicitBinding.js · JAVASCRIPT
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
// ── call: invoke immediately with a specific 'this' ──────────
const userProfile = {
  username: 'alex_codes',
  memberSince: 2021
};

function greetUser(greeting, punctuation) {
  // 'this' will be whatever we pass as the first arg to .call()
  console.log(`${greeting}, ${this.username}! Member since ${this.memberSince}${punctuation}`);
}

// Force 'this' to be userProfile, pass the rest as normal args
greetUser.call(userProfile, 'Welcome back', '!');
// Welcome back, alex_codes! Member since 2021!

// ── apply: same idea, but arguments go in an array ───────────
greetUser.apply(userProfile, ['Hello', '.']);
// Hello, alex_codes! Member since 2021.

// ── Borrowing array methods with call ────────────────────────
// arguments is array-LIKE but doesn't have .map, .filter etc.
function listArguments() {
  // Borrow Array's join method and run it with 'arguments' as 'this'
  const joined = Array.prototype.join.call(arguments, ' | ');
  console.log('Arguments joined:', joined);
}

listArguments('HTML', 'CSS', 'JavaScript');
// Arguments joined: HTML | CSS | JavaScript

// ── bind: create a permanently bound function ─────────────────
const notificationService = {
  serviceName: 'AlertHub',
  send(message) {
    // Without bind, 'this' would be lost inside setTimeout
    console.log(`[${this.serviceName}] ${message}`);
  }
};

// bind returns a NEW function — this is forever locked to notificationService
const boundSend = notificationService.send.bind(notificationService);

// Even inside setTimeout (where 'this' would normally be global), it works.
setTimeout(boundSend, 100, 'Server connection established.');
// [AlertHub] Server connection established.

// ── bind with pre-filled arguments (partial application) ─────
function formatPrice(currency, amount) {
  return `${currency}${amount.toFixed(2)} charged to ${this.username}`;
}

const formatInGBP = formatPrice.bind(userProfile, '£');
console.log(formatInGBP(49.9));   // £49.90 charged to alex_codes
console.log(formatInGBP(199));    // £199.00 charged to alex_codes
▶ Output
Welcome back, alex_codes! Member since 2021!
Hello, alex_codes! Member since 2021.
Arguments joined: HTML | CSS | JavaScript
[AlertHub] Server connection established.
£49.90 charged to alex_codes
£199.00 charged to alex_codes
⚠️
Pro Tip: bind for Callbacks, call for BorrowingUse bind whenever you're passing a method somewhere you don't control (setTimeout, event listeners, array callbacks). Use call when you want to borrow a method from one object and immediately run it on another — classic use case is Array.prototype methods on array-like objects.

Arrow Functions and this — Why They Behave Completely Differently

Arrow functions don't just look different from regular functions — they fundamentally work differently when it comes to this. A regular function creates its own this binding every time it's called. An arrow function has no this of its own at all. Instead, it captures the this value from the surrounding lexical scope at the moment it's defined — and that value is frozen forever.

This isn't a quirk; it's a deliberate design decision to solve the classic 'this in a callback' problem that plagued pre-ES6 JavaScript. Before arrow functions, developers had to write const self = this or const that = this to preserve the outer context inside a nested function. Arrow functions make that pattern obsolete.

But this 'no own this' behavior is a double-edged sword. Arrow functions are perfect for inline callbacks inside class methods or object methods. They're the wrong choice for object methods themselves, because the enclosing scope of an object literal is usually the module or global scope — not the object. You'll get a this that points to the wrong place entirely.

The rule of thumb: use arrow functions inside methods. Use regular functions as methods.

thisArrowFunctions.js · JAVASCRIPT
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
// ── The classic pre-ES6 problem arrow functions solve ────────
const orderQueue = {
  restaurantName: 'The Code Bistro',
  pendingOrders: ['Burger', 'Salad', 'Pizza'],

  // Regular function — creates its OWN 'this' binding
  printOrdersOldWay() {
    const self = this; // Capture outer 'this' before entering callback

    this.pendingOrders.forEach(function(order) {
      // Inside this regular function callback, 'this' is NOT orderQueue
      // That's why we had to use 'self' as a workaround
      console.log(`[Old Way] ${self.restaurantName}: ${order}`);
    });
  },

  // Arrow function inside — captures 'this' from printOrdersModern's context
  printOrdersModern() {
    // 'this' here is orderQueue (implicit binding from the dot call)
    this.pendingOrders.forEach((order) => {
      // Arrow function inherits 'this' from printOrdersModern — it's orderQueue
      console.log(`[Modern] ${this.restaurantName}: ${order}`);
    });
  }
};

orderQueue.printOrdersOldWay();
orderQueue.printOrdersModern();

// ── Arrow functions as METHODS — what goes wrong ──────────────
const userAccount = {
  accountHolder: 'Jordan',

  // DON'T do this — arrow function as a method
  greetArrow: () => {
    // 'this' here is captured from the surrounding scope of the object LITERAL
    // which is the global scope (or module scope) — NOT userAccount
    console.log(`Arrow method this.accountHolder: ${this?.accountHolder}`);
  },

  // DO this — regular function as a method
  greetRegular() {
    // 'this' is userAccount because we call it with userAccount.greetRegular()
    console.log(`Regular method this.accountHolder: ${this.accountHolder}`);
  }
};

userAccount.greetArrow();   // Arrow method this.accountHolder: undefined
userAccount.greetRegular(); // Regular method this.accountHolder: Jordan

// ── Arrow functions correctly used inside a class ─────────────
class CountdownTimer {
  constructor(label, seconds) {
    this.label = label;      // 'this' is the new instance
    this.seconds = seconds;
  }

  start() {
    // Arrow function captures 'this' = the CountdownTimer instance
    const tick = () => {
      this.seconds -= 1;
      console.log(`${this.label}: ${this.seconds}s remaining`);
    };

    // No binding needed — arrow function keeps 'this' correctly
    const interval = setInterval(() => {
      tick();
      if (this.seconds <= 0) clearInterval(interval);
    }, 1000);
  }
}

const timer = new CountdownTimer('Deploy Build', 3);
timer.start();
// (after 1s) Deploy Build: 2s remaining
// (after 2s) Deploy Build: 1s remaining
// (after 3s) Deploy Build: 0s remaining
▶ Output
[Old Way] The Code Bistro: Burger
[Old Way] The Code Bistro: Salad
[Old Way] The Code Bistro: Pizza
[Modern] The Code Bistro: Burger
[Modern] The Code Bistro: Salad
[Modern] The Code Bistro: Pizza
Arrow method this.accountHolder: undefined
Regular method this.accountHolder: Jordan
(after 1s) Deploy Build: 2s remaining
(after 2s) Deploy Build: 1s remaining
(after 3s) Deploy Build: 0s remaining
🔥
Interview Gold: Arrow Functions Can't Be ReboundCalling .bind(), .call(), or .apply() on an arrow function silently does nothing to its 'this'. The this value is baked in at definition time and is completely immutable. Interviewers love asking this — most candidates assume bind works universally.

this Inside Classes — new Binding and Why Constructors Work

When you use the new keyword to create an instance from a class (or constructor function), JavaScript performs a specific sequence behind the scenes: it creates a brand-new empty object, sets this to point to that object inside the constructor, runs your constructor code, and then returns the new object automatically. This is called new binding, and it's the highest-priority binding rule.

This is why class constructors feel intuitive — every this.property you write in a constructor is safely writing onto the new instance, not some shared global. Each call to new produces a completely independent object with its own this.

Class methods work via the prototype chain. When you call instance.doSomething(), JavaScript finds doSomething on the prototype, but the call site still has a dot — so this is the instance. It's implicit binding applied to prototype methods.

The one sharp edge in classes: if you pass a class method as a callback without binding it first, you lose the instance context — the same trap as with plain objects. Modern React class components historically addressed this by either binding in the constructor or using class field arrow functions. Understanding why those patterns exist makes you a significantly stronger developer.

thisClassBinding.js · JAVASCRIPT
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
// ── new Binding — what 'new' does behind the scenes ──────────
class ShoppingCart {
  constructor(ownerName) {
    // At this point, 'this' is a brand-new empty object created by 'new'
    this.owner = ownerName;  // Attaches 'owner' to that new object
    this.items = [];          // Each instance gets its OWN items array
    this.total = 0;
    console.log(`Cart created for: ${this.owner}`);
  }

  addItem(productName, price) {
    // 'this' is the specific ShoppingCart instance this was called on
    this.items.push({ productName, price });
    this.total += price;
    console.log(`Added ${productName} (£${price}) → Cart total: £${this.total.toFixed(2)}`);
  }

  getSummary() {
    return `${this.owner}'s cart: ${this.items.length} item(s), Total: £${this.total.toFixed(2)}`;
  }
}

const aliceCart = new ShoppingCart('Alice');  // new creates a fresh object for Alice
const bobCart   = new ShoppingCart('Bob');    // Completely separate object for Bob

aliceCart.addItem('JavaScript Book', 29.99);
aliceCart.addItem('Mechanical Keyboard', 89.99);
bobCart.addItem('Webcam', 49.99);

console.log(aliceCart.getSummary());
console.log(bobCart.getSummary());

// ── The class callback trap — and two ways to fix it ──────────
class NotificationBell {
  constructor(soundName) {
    this.soundName = soundName;

    // FIX OPTION 1: Bind in the constructor
    // this.ring is now a bound copy — safe to use as a callback
    this.ring = this.ring.bind(this);
  }

  ring() {
    console.log(`🔔 Playing sound: ${this.soundName}`);
  }
}

const alertBell = new NotificationBell('chime');

// Safe to pass as a callback — 'this' is locked to alertBell
setTimeout(alertBell.ring, 50);

// FIX OPTION 2: Class field arrow function (modern approach)
class MessageAlert {
  constructor(text) {
    this.text = text;
  }

  // Class field arrow captures 'this' at construction time
  // No need to bind manually — works safely as a callback
  show = () => {
    console.log(`📢 Alert: ${this.text}`);
  };
}

const loginAlert = new MessageAlert('Login successful');
setTimeout(loginAlert.show, 100); // Works perfectly — no bind needed
▶ Output
Cart created for: Alice
Cart created for: Bob
Added JavaScript Book (£29.99) → Cart total: £29.99
Added Mechanical Keyboard (£89.99) → Cart total: £119.98
Added Webcam (£49.99) → Cart total: £49.99
Alice's cart: 2 item(s), Total: £119.98
Bob's cart: 1 item(s), Total: £49.99
🔔 Playing sound: chime
📢 Alert: Login successful
⚠️
Pro Tip: Class Field Arrow vs Constructor BindClass field arrows (show = () => {}) create a new function per instance — slightly more memory. Constructor .bind() does the same. Both are correct; class field arrows are cleaner and preferred in modern code. Regular prototype methods share one function across all instances, which is more memory-efficient when you don't need binding.
Context / ScenarioWhat 'this' Points ToHow to Control It
Plain function call (non-strict)Global object (window / global)Use strict mode or restructure the call
Plain function call (strict mode)undefinedCall it as a method or use .call()
Method call via dot notationThe object left of the dotEnsure you always call it via the object
Arrow functionLexical scope where it was defined (never changes)Cannot be rebound — pick your definition site carefully
new keyword (constructor / class)The newly created instanceAlways use new — never call constructors without it
call / applyFirst argument you pass inFull manual control at each invocation
bindFirst argument, permanently lockedCreates a new reusable function — ideal for callbacks
Event listener (regular function)The DOM element that fired the eventUse arrow function or .bind(this) to override
Event listener (arrow function)Lexical scope (not the DOM element)Use regular function if you need the element as this

🎯 Key Takeaways

  • this is determined by the call site, not the definition site — ask yourself 'how is this function being invoked right now?' not 'where was it written?'
  • Arrow functions have no own this — they inherit it from the surrounding lexical scope permanently, and cannot be rebound with bind, call, or apply
  • The new keyword creates a fresh object and sets this to it inside the constructor — each instance gets its own isolated this
  • Use bind when passing methods as callbacks, use call/apply when borrowing methods or making one-off invocations with a custom this — and use arrow functions inside methods to avoid losing context in nested callbacks

⚠ Common Mistakes to Avoid

  • Mistake 1: Passing a method as a callback without binding — Symptom: 'this.username' or 'this.setState' is undefined inside the callback, even though it works fine when called directly — Fix: Use .bind(this) when passing the method (e.g. onClick={this.handleClick.bind(this)}) or switch to a class field arrow function (handleClick = () => {...}) so this is captured at construction time.
  • Mistake 2: Using an arrow function as an object method — Symptom: this inside the method is the global object or undefined, so this.propertyName returns undefined even though the property clearly exists on the object — Fix: Always define object methods using regular function syntax (either method shorthand greet() {} or greet: function() {}). Reserve arrow functions for callbacks nested inside those methods.
  • Mistake 3: Trying to use .bind(), .call(), or .apply() to change this inside an arrow function — Symptom: The function runs but this is still the lexical scope value — no error is thrown, making this silent and very hard to debug — Fix: Understand that arrow functions permanently capture their lexical this at definition time. If you need a rebindable function, use a regular function expression or declaration instead.

Interview Questions on This Topic

  • QWhat are the four binding rules that determine what 'this' refers to in JavaScript, and in what priority order are they applied?
  • QExplain why calling .bind() on an arrow function has no effect on its 'this' value. How does an arrow function's 'this' get determined?
  • QGiven a class with a method assigned to a button's event listener as 'button.addEventListener("click", this.handleClick)', why would 'this' inside handleClick be the button element and not the class instance — and what are two different ways to fix it?

Frequently Asked Questions

Why does 'this' return undefined inside a regular function in JavaScript?

If your code is running in strict mode ('use strict'), calling a plain function with no object context means 'this' defaults to undefined instead of the global object. This is intentional — strict mode removes the confusing automatic fallback to the global scope. The fix is to either call the function as a method of an object, or use .call(yourObject) to explicitly provide a this value.

What is the difference between .call() and .apply() in JavaScript?

Both call and apply invoke a function immediately with a specified 'this' value. The only difference is argument passing: .call(thisArg, arg1, arg2) takes arguments individually comma-separated, while .apply(thisArg, [arg1, arg2]) takes them as a single array. A useful memory device — Apply = Array.

Can you change what 'this' refers to inside an arrow function using bind?

No — calling .bind(), .call(), or .apply() on an arrow function does not change its 'this'. Arrow functions capture 'this' from the surrounding lexical scope at the moment they're defined, and that value is permanently locked in. The call succeeds without errors but 'this' remains unchanged, which is why this mistake can be very hard to spot. If you need a rebindable function, use a regular function instead.

🔥
TheCodeForge Editorial Team Verified Author

Written and reviewed by senior developers with real-world experience across enterprise, startup and open-source projects. Every article on TheCodeForge is written to be clear, accurate and genuinely useful — not just SEO filler.

← PreviousScope and Hoisting in JavaScriptNext →null vs undefined in JavaScript
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged