The this Keyword
this is not fixed by where a function is defined — it is determined by how the function is called. The same function can have a different this on every call. There are four binding rules, checked in priority order.
🎯 What you'll learn
- The Core Idea
- Rule 1 — Default Binding (plain function call)
- Rule 2 — Implicit Binding (method call)
- Rule 3 — Explicit Binding (
call,apply,bind) - Rule 4 —
newBinding (constructor call)
1The Core Idea
this is not fixed by where a function is defined — it is determined by how the function is called. The same function can have a different this on every call. There are four binding rules, checked in priority order.
2Rule 1 — Default Binding (plain function call)
function whoAmI() {
console.log(this);
}
whoAmI();
// non-strict mode: globalThis (window / global)
// strict mode ("use strict"): undefinedCalling a function with no receiver (fn(), not obj.fn()) triggers default binding. Always assume strict mode in modern code (ES modules are strict by default) — this will be undefined, not the global object.
3Rule 2 — Implicit Binding (method call)
const user = {
name: "Ana",
greet() {
console.log(`Hi, I'm ${this.name}`);
},
};
user.greet(); // "Hi, I'm Ana" — this === userWhatever object is immediately to the left of the dot at the call site becomes this. This is purely about the call site, not where the function was defined.
const greetFn = user.greet;
greetFn(); // "Hi, I'm undefined" — this === undefined (strict) — lost its receiver!This is the single most common this bug: extracting a method as a standalone reference loses implicit binding.
4Rule 3 — Explicit Binding (call, apply, bind)
function greet() {
console.log(`Hi, I'm ${this.name}`);
}
const person = { name: "Leo" };
greet.call(person); // "Hi, I'm Leo" — invoke now, this = person
greet.apply(person); // same as call, but arguments passed as an array
const bound = greet.bind(person);
bound(); // "Hi, I'm Leo" — this permanently fixed to person| Method | Invokes immediately? | Arguments | this |
|---|---|---|---|
.call(thisArg, a, b, ...) | ✅ yes | comma-separated | set to thisArg |
.apply(thisArg, [a, b]) | ✅ yes | array | set to thisArg |
.bind(thisArg, a, b, ...) | ❌ no — returns a new function | pre-filled, then more allowed at call time | permanently set to thisArg |
bind creates a new function whose this cannot be overridden later, even by implicit binding.
5Rule 4 — new Binding (constructor call)
function User(name) {
this.name = name;
}
const u = new User("Mia");
console.log(u.name); // "Mia"new creates a brand-new empty object, sets this to that object inside the function, and (if the function doesn't explicitly return an object) returns it automatically.
6Priority Order
When multiple rules could apply, this order wins:
new binding > explicit binding (call/apply/bind) > implicit binding (obj.method()) > default binding (plain call)A function created with bind() cannot have its this changed by new in most engines' practical usage patterns for regular functions — but bind-produced functions can still be used as constructors in edge cases; this is rarely relied upon in real code.
1Arrow Functions Don't Have Their Own this
Arrow functions have no this binding of their own. Instead, they capture this lexically from the enclosing scope at the time they are defined — exactly like a normal variable would be captured by a closure. None of the four binding rules from Part 1 apply to them.
const user = {
name: "Ana",
greetRegular: function () {
console.log(this.name); // "Ana" — implicit binding, this = user
},
greetArrow: () => {
console.log(this.name); // undefined — this comes from the OUTER scope, not user
},
};
user.greetRegular();
user.greetArrow();2Why This Matters for Callbacks
Arrow functions are the standard fix for the classic "lost this" bug in callbacks:
class Timer {
constructor() {
this.seconds = 0;
}
startBroken() {
setInterval(function () {
this.seconds++; // this is undefined/global here — NOT the Timer instance
}, 1000);
}
startFixed() {
setInterval(() => {
this.seconds++; // arrow captures `this` from startFixed(), which is the instance
}, 1000);
}
}setInterval calls its callback as a plain function call (default binding), so a regular function loses the instance. An arrow function captures this from startFixed itself, where implicit binding already set it correctly.
3call/apply/bind Do Nothing to Arrow Functions
const arrow = () => console.log(this);
arrow.call({ name: "ignored" }); // this is still whatever it was lexically — unaffectedBecause arrows never have their own this, none of the explicit-binding methods can change it.
4Arrow Functions Cannot Be Constructors
const Foo = () => {};
new Foo(); // TypeError: Foo is not a constructor5this in Class Methods
Regular class methods behave like object methods — this depends on the call site:
class Counter {
count = 0;
increment() { this.count++; }
}
const c = new Counter();
const detached = c.increment;
// detached(); // TypeError-ish: this.count fails, this is undefined (strict mode, class bodies are strict)Class field arrow functions solve this by permanently binding this to the instance:
class Counter {
count = 0;
increment = () => { this.count++; }; // arrow class field — this always === instance
}
const c = new Counter();
const detached = c.increment;
detached(); // works — this is bound to c, regardless of how it's calledTradeoff: each instance gets its own copy of increment (not shared on the prototype), costing a bit more memory per instance versus a regular method.
6Decision Table
| Situation | Use |
|---|---|
Object method that reads this from its receiver | regular function/method shorthand |
Callback passed to setTimeout/setInterval/array methods, needing the outer this | arrow function |
Need this permanently fixed regardless of call site | .bind() or an arrow class field |
Need a constructor (new) | regular function or class — never an arrow |
⚠️ Common Pitfalls
- Detaching a method (
const fn = obj.method; fn();) — loses implicit binding. Fix with.bind(obj)or call asobj.method(). - Using an arrow function as an object literal method expecting
thisto be the object — it won't be; arrows ignore implicit binding entirely. - Forgetting
bindreturns a new function —fn.bind(obj)does not mutatefn; you must use the returned value. - Assuming class methods auto-bind — only class-field arrow functions do; regular class methods behave like any other function regarding
this.
🎤 Interview Q&A
Q1How is this determined?core▾
By how a function is called: method call obj.f() → obj; plain call f() → undefined in strict mode (global object otherwise); new F() → the new object; call/apply/bind → explicitly set; arrow functions capture this from the enclosing scope.
- Order of precedence? new > explicit bind/call > method call > default.
Q2Why do arrow functions behave differently with this?core▾
They don't have their own this (or arguments); they use the lexical this of the surrounding code, which makes them ideal for callbacks inside methods but unsuitable as object methods needing dynamic this.
- Can you bind an arrow function? No; bind has no effect on its this.
Q3How do you lose this and how do you fix it?core▾
Passing a method as a callback (setTimeout(obj.method)) detaches it from obj. Fix with obj.method.bind(obj), an arrow wrapper () => obj.method(), or class field arrow functions.
- Class field arrow trade-off? A new function per instance, not on the prototype.
Q4call vs apply vs bind?core▾
call(thisArg, a, b) and apply(thisArg, [a, b]) invoke immediately with a given this; bind(thisArg, ...args) returns a new function permanently bound (with optional partial arguments).
- Can a bound function be rebound? No.
Q5What is this at the top level of an ES module?senior▾
undefined, because modules run in strict mode; in classic scripts it's the global object.
- In Node CommonJS?
module.exports.
🛠️ Practice exercises
Each exercise is a file in this topic folder. Code exercises are self-checking: run them and make the checks pass.