The this Keyword in JavaScript Explained — Context, Binding and Real-World Patterns
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.
// ── 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
Machine brand: BrewMaster 3000
Machine brand: undefined
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.
// ── 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
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
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.
// ── 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
[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
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.
// ── 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
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
| Context / Scenario | What 'this' Points To | How to Control It |
|---|---|---|
| Plain function call (non-strict) | Global object (window / global) | Use strict mode or restructure the call |
| Plain function call (strict mode) | undefined | Call it as a method or use .call() |
| Method call via dot notation | The object left of the dot | Ensure you always call it via the object |
| Arrow function | Lexical scope where it was defined (never changes) | Cannot be rebound — pick your definition site carefully |
| new keyword (constructor / class) | The newly created instance | Always use new — never call constructors without it |
| call / apply | First argument you pass in | Full manual control at each invocation |
| bind | First argument, permanently locked | Creates a new reusable function — ideal for callbacks |
| Event listener (regular function) | The DOM element that fired the event | Use 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.
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.