this is determined by how a function is called, not where it's defined
Four binding rules: default, implicit, explicit, new — applied in priority
Arrow functions have no own this; they inherit it lexically and cannot be rebound
Use bind for callbacks you don't control; use call/apply for one-off invocations
Performance: Creating bound functions per render loop wastes memory — pre-bind or use class field arrows
Biggest mistake: Passing a method as a callback without binding — this becomes the caller, not your object
Plain-English First
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.
You've seen the bug: a callback suddenly can't find a method it definitely should have access to. 'this.name' returns undefined inside a perfectly normal-looking function. That's the chaos this causes. It's one of the most misunderstood features in the language, tripping up developers at every 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 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.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// ── Default Binding ──────────────────────────────────────────// Called as a plain function — no object in front of it.functiondescribeWeather() {
// 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 Site
JavaScript 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.
Production Insight
In a production Node.js microservice, a developer extracted a database query method and passed it to a middleware as a callback.
The method referenced this.config inside, which became the global object — causing silent connection failures.
Fix: always bind methods before passing them, or use arrow functions to preserve the outer this.
Key Takeaway
this is determined by the call site, not the definition site.
Ask 'how is this function invoked?' not 'where was it written?'.
The call site is everything.
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.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// ── call: invoke immediately with a specific 'this' ──────────const userProfile = {
username: 'alex_codes',
memberSince: 2021
};
functiongreetUser(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.functionlistArguments() {
// 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 notificationServiceconst 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) ─────functionformatPrice(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 Borrowing
Use 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.
Production Insight
A logging library borrowed console.log via call to add timestamps. The team accidentally passed null as this, causing 'Cannot convert undefined to object' errors in production.
Rule: when using call/apply, always provide a valid object as the first argument — even if the function doesn't technically use this.
Key Takeaway
bind locks this permanently for life; call/apply set it per invocation.
Use bind for callbacks, call for borrowing, apply for dynamic argument lists.
Prefer call over apply unless arguments come as an array.
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.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// ── The classic pre-ES6 problem arrow functions solve ────────const orderQueue = {
restaurantName: 'The Code Bistro',
pendingOrders: ['Burger', 'Salad', 'Pizza'],
// Regular function — creates its OWN 'this' bindingprintOrdersOldWay() {
const self = this; // Capture outer 'this' before entering callbackthis.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(`[OldWay] ${self.restaurantName}: ${order}`);
});
},
// Arrow function inside — captures 'this' from printOrdersModern's contextprintOrdersModern() {
// '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 methodgreetRegular() {
// '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 ─────────────classCountdownTimer {
constructor(label, seconds) {
this.label = label; // 'this' is the new instancethis.seconds = seconds;
}
start() {
// Arrow function captures 'this' = the CountdownTimer instanceconst tick = () => {
this.seconds -= 1;
console.log(`${this.label}: ${this.seconds}s remaining`);
};
// No binding needed — arrow function keeps 'this' correctlyconst interval = setInterval(() => {
tick();
if (this.seconds <= 0) clearInterval(interval);
}, 1000);
}
}
const timer = newCountdownTimer('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 Rebound
Calling .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.
Production Insight
A team used arrow functions for all methods in a React class component — they couldn't understand why this.props was undefined.
The lexical this captured the module scope, not the component instance.
Fix: only use arrow functions inside methods, not as methods themselves.
Key Takeaway
Arrow functions have no own this — they inherit from the enclosing scope and are immutable.
Use them inside methods to avoid callback binding, but never as object or class methods.
Cannot be rebound: .bind() on an arrow does nothing.
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.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// ── new Binding — what 'new' does behind the scenes ──────────classShoppingCart {
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 arraythis.total = 0;
console.log(`Cart created for: ${this.owner}`);
}
addItem(productName, price) {
// 'this' is the specific ShoppingCart instance this was called onthis.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 ──────────classNotificationBell {
constructor(soundName) {
this.soundName = soundName;
// FIX OPTION 1: Bind in the constructor// this.ring is now a bound copy — safe to use as a callbackthis.ring = this.ring.bind(this);
}
ring() {
console.log(`🔔 Playing sound: ${this.soundName}`);
}
}
const alertBell = newNotificationBell('chime');
// Safe to pass as a callback — 'this' is locked to alertBellsetTimeout(alertBell.ring, 50);
// FIX OPTION 2: Class field arrow function (modern approach)classMessageAlert {
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 = newMessageAlert('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
Class 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.
Production Insight
A legacy codebase using React.createClass was migrated to ES6 classes. Hundreds of event handlers broke because this was no longer autobound to the instance.
The fix: add constructor binding to every method passed as a callback — or upgrade to class field arrows.
Lesson: understand how new binding works to anticipate breaking changes during refactors.
Key Takeaway
new creates a fresh object and sets this to it in the constructor.
Class methods use implicit binding — pass them as callbacks and you lose it.
Bind in constructor or use class field arrows to lock this.
Debugging this Binding Issues in Production
When this goes wrong in production, the symptom is often silent — undefined is not a function, or state doesn't update. No stack trace points directly to the binding issue. You need a systematic approach to find the culprit.
The first step is always to log this at the call site. Add console.log(this) right before the problematic line. Then execute the function in different ways: direct call, callback, event listener. See how this changes.
Next, use the debugger statement to pause execution and inspect the call stack. The call stack shows the chain of function calls leading to the current code. It tells you exactly which object is 'in front of the dot' — or if there's none.
For React specifically, use React DevTools to inspect component state and props. If a handler isn't updating state, check that the handler is properly bound. The Components tab shows the component instance — if this is undefined, you've lost the binding.
Another quick technique: wrap the problematic call in an arrow function. If wrapping it in (args) => this.method(args) fixes the issue, it confirms a binding problem. Use that as a temporary fix and then refactor to proper binding.
thisDebugging.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// ── Systematic Debugging ────────────────────────────────────const userService = {
name: 'UserService',
fetchUser(id) {
// Step 1: Log this at the call site
console.log('this inside fetchUser:', this);
console.trace(); // Shows the call stack// If this is wrong, check where this function is being calledreturn { id, name: 'Alice' };
}
};
// Direct call — this is userService
userService.fetchUser(1); // this: {name: 'UserService', fetchUser: f}// Extracting without binding — this becomes undefined (strict) or globalconst fetch = userService.fetchUser;
fetch(2); // this: undefined (strict) or Window// Fix: bindconst boundFetch = userService.fetchUser.bind(userService);
boundFetch(3); // this: userService// ── Using debugger to inspect ────────────────────────────────functionproblematicHandler(event) {
debugger; // Execution pauses here — inspect 'this' in dev tools
console.log('Clicked:', this); // Often becomes the DOM element
}
document.querySelector('button')?.addEventListener('click', problematicHandler);
// ── React DevTools check ─────────────────────────────────────// Open React DevTools, Components tab, select component// Verify that the handler function shows 'bound' or arrow in its name// ── Quick wrap test ──────────────────────────────────────────// If this.block fails as a callback, try:setTimeout(() => userService.fetchUser(4), 100);
// If that works, you have a binding issue. Temporarily fix with arrow, then bind permanently.
Output
this inside fetchUser: {name: 'UserService', fetchUser: f}
(call stack)
this inside fetchUser: undefined (strict mode)
this inside fetchUser: {name: 'UserService', fetchUser: f}
(debugger pauses)
Pro Tip: Use console.trace() for Call Site
console.trace() prints the call stack at that moment. It shows you exactly which function called your function, and whether there's a dot before the call. This is the fastest way to confirm default vs implicit binding.
Production Insight
A production incident: a form submission handler on a class component silently failed because the constructor bound the wrong method name (typo). No error, but state never updated. The team spent hours because they assumed binding, but they never verified with console.log(this).
Fix: always log this in event handlers during development to catch binding typos early.
Key Takeaway
Debug this issues by logging this at the call site and inspecting the call stack.
Use the arrow wrapper test to confirm a binding problem.
In dev, log this in every event handler until you trust the binding.
● Production incidentPOST-MORTEMseverity: high
Lost Context in React Event Handler Causes Silent UI Failure
Symptom
Clicking the submit button does nothing. No JavaScript error in the browser console. State remains unchanged.
Assumption
The event handler is defined on the class, so this refers to the class instance automatically.
Root cause
The handler was passed as a callback to the button's onClick prop without binding this. Inside the handler, this is the button DOM element, not the class instance. Code like this.setState fails silently (undefined is not an object).
Fix
Bind the method in the constructor: this.handleSubmit = this.handleSubmit.bind(this) or use class field arrow syntax: handleSubmit = () => {...}.
Key lesson
Any method passed as a callback in React class components must be bound.
Class field arrow functions are the cleanest fix — they capture this at construction time.
Always test button clicks with state updates in React; missed this is a silent failure.
Production debug guideSymptom-to-action guide for when this goes wrong.5 entries
Symptom · 01
this is undefined inside a function
→
Fix
Check if the code is running in strict mode ('use strict'). In strict mode, default binding gives undefined. If not strict, this points to globalThis. Use .call() or .bind() to provide the correct context.
Symptom · 02
Method works when called directly but fails as a callback
→
Fix
You've lost implicit binding. The method is extracted into a variable and called without the dot. Fix by binding the method: myObj.method.bind(myObj) or use an arrow function wrapper.
Symptom · 03
this in an arrow function doesn't respond to .bind()
→
Fix
That's expected. Arrow functions capture this lexically and are immutable. If you need a rebindable function, refactor to a regular function expression.
Symptom · 04
Event listener's this is the DOM element, not your object
→
Fix
For regular functions passed to addEventListener, this is the element. To preserve your object context, use .bind(obj) or wrap with an arrow function: element.addEventListener('click', (e) => handler(e)).
Symptom · 05
Class method called inside setTimeout has wrong this
→
Fix
setTimeout calls the function with default binding (global or undefined). Pass the method bound: setTimeout(this.myMethod.bind(this), 1000) or use an arrow function inside the class method.
★ Quick 'this' Debugging Cheat SheetImmediate steps to diagnose and fix this binding issues.
this is not what you expect anywhere−
Immediate action
Log this at the call site with console.log(this) just before the problematic line.
Commands
console.log('Current this:', this);
debugger; // pause execution and inspect call stack
Fix now
Use .bind() to lock this: func.bind(correctContext)
React event handler not updating state+
Immediate action
Check if the handler is bound in the constructor or use class field arrow.
Commands
console.log('this inside handler:', this); // look for class instance vs undefined
document.querySelector('button').__reactProps$... ; // inspect bound handler via React DevTools
Fix now
In constructor: this.handleClick = this.handleClick.bind(this); or change to handleClick = () => {}
Arrow function's this is still wrong after .call()+
Immediate action
It's not wrong — arrow functions ignore .call/.bind. Check the lexical scope where it was defined.
Commands
console.log('Arrow defined in scope where this is:', this); // before arrow definition
Refactor to regular function if you need dynamic this.
Fix now
Replace arrow with function expression and bind explicitly.
this Binding Reference Table
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
1
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?'
2
Arrow functions have no own this
they inherit it from the surrounding lexical scope permanently, and cannot be rebound with bind, call, or apply
3
The new keyword creates a fresh object and sets this to it inside the constructor
each instance gets its own isolated this
4
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
3 patterns
×
Passing a method as a callback without binding
Symptom
Inside the callback, 'this.username' or 'this.setState' is undefined even though it works fine when called directly on the object.
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.
×
Using an arrow function as an object method
Symptom
Inside the method, this is the global object or undefined, so this.propertyName returns undefined even though the property exists on the object.
Fix
Define object methods using regular function syntax (method shorthand greet() {} or greet: function() {}). Reserve arrow functions for callbacks nested inside those methods.
×
Trying to use .bind(), .call(), or .apply() to change this inside an arrow function
Symptom
No error is thrown, but this is still the lexical scope value — making this mistake silent and 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 PREP · PRACTICE MODE
Interview Questions on This Topic
Q01SENIOR
What are the four binding rules that determine what 'this' refers to in ...
Q02SENIOR
Explain why calling .bind() on an arrow function has no effect on its 't...
Q03SENIOR
Given a class with a method assigned to a button's event listener as 'bu...
Q01 of 03SENIOR
What are the four binding rules that determine what 'this' refers to in JavaScript, and in what priority order are they applied?
ANSWER
The four rules are: 1) new binding — when a function is called with 'new', this is the newly created instance (highest priority). 2) Explicit binding — when using call, apply, or bind, this is the first argument passed. 3) Implicit binding — when a function is called as a method of an object (dot notation), this is that object. 4) Default binding — when none of the above apply, this is the global object (or undefined in strict mode). The priority order from highest to lowest is: new > explicit > implicit > default.
Q02 of 03SENIOR
Explain why calling .bind() on an arrow function has no effect on its 'this' value. How does an arrow function's 'this' get determined?
ANSWER
Arrow functions do not have their own 'this' binding. Instead, they capture the 'this' value from the surrounding lexical scope at the moment they are defined. This captured 'this' is permanent and immutable. When you call .bind() on an arrow function, it returns a new function, but the 'this' inside the arrow function remains unchanged because the arrow function ignores the 'this' argument provided by bind, call, or apply. The arrow function's 'this' is determined entirely by where it was defined — not by how it's called.
Q03 of 03SENIOR
Given 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?
ANSWER
When a regular function is passed to addEventListener, the browser sets 'this' inside the handler to the DOM element that fired the event (the button). The method loses its original class instance context because it's called as a plain function by the event system, not as a method of the class instance. To fix it: 1) Bind the method in the constructor: 'this.handleClick = this.handleClick.bind(this);' which creates a permanently bound version. 2) Use a class field arrow function: 'handleClick = () => { ... }', which captures 'this' from the class instance at construction time and ignores the event system's 'this' assignment.
01
What are the four binding rules that determine what 'this' refers to in JavaScript, and in what priority order are they applied?
SENIOR
02
Explain why calling .bind() on an arrow function has no effect on its 'this' value. How does an arrow function's 'this' get determined?
SENIOR
03
Given 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?
SENIOR
FAQ · 4 QUESTIONS
Frequently Asked Questions
01
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.
Was this helpful?
02
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.
Was this helpful?
03
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.
Was this helpful?
04
What happens to 'this' inside a setTimeout callback?
setTimeout calls the callback as a plain function, so 'this' defaults to the global object (or undefined in strict mode). If you need 'this' to refer to an outer object, you must pass a bound version: setTimeout(this.myMethod.bind(this), 1000) or use an arrow function that captures the outer this.