The JavaScript this Keyword: Full Deep Dive
A complete deep dive into JavaScript's this keyword. Understand how this is determined by call site, not by where code is written. Covers global context, method calls, explicit binding, new binding, class methods, and the rules for determining this in every situation.
this is one of the most commonly misunderstood mechanisms in JavaScript. Unlike most languages where this refers to the current object instance at definition time, JavaScript's this is determined entirely at call time — by how a function is invoked, not where it is written. Once you understand the four binding rules, this becomes completely predictable.
The Golden Rule: this Is Set at Call Time
function whoAmI() {
console.log(this);
}
const obj = { whoAmI };
whoAmI(); // globalThis (or undefined in strict mode)
obj.whoAmI(); // obj — same function, different thisSame function. Two different call sites. Two different this values. The call site determines this.
Rule 1: Default Binding
When a function is called as a plain function (no receiver, no new, no explicit binding), this gets the default binding:
function showThis() {
console.log(this);
}
showThis(); // window (browser) / global (Node.js)In strict mode ("use strict" or inside a class/module), the default binding is undefined:
"use strict";
function strictThis() {
console.log(this); // undefined
}
strictThis();Rule 2: Implicit Binding
When a function is called as a method on an object, this is the object to the left of the dot:
const person = {
name: "Alice",
greet() {
return `Hi, I'm ${this.name}`;
},
};
console.log(person.greet()); // "Hi, I'm Alice" — this = personThe binding is always the immediate caller — the last object in the chain:
const outer = {
name: "Outer",
inner: {
name: "Inner",
greet() { return this.name; },
},
};
console.log(outer.inner.greet()); // "Inner" — this = outer.inner, not outerImplicit Binding Loss
The binding is lost when the method reference is detached from its object:
const user = {
name: "Bob",
sayName() { return this.name; },
};
const fn = user.sayName; // Reference copied — detached from user
fn(); // undefined (strict mode) — THIS IS THE INFAMOUS CONTEXT LOSSThis is the source of most this-related bugs. See losing this in JavaScript callbacks for a full treatment.
Rule 3: Explicit Binding (call, apply, bind)
You can explicitly set this using call(), apply(), and bind():
function introduce(greeting, punctuation) {
return `${greeting}, I'm ${this.name}${punctuation}`;
}
const alice = { name: "Alice" };
const bob = { name: "Bob" };
// call: arguments one by one
introduce.call(alice, "Hello", "!"); // "Hello, I'm Alice!"
introduce.call(bob, "Hey", "."); // "Hey, I'm Bob."
// apply: arguments as array
introduce.apply(alice, ["Hi", "?"]); // "Hi, I'm Alice?"
// bind: returns a new function with this permanently bound
const aliceIntro = introduce.bind(alice);
aliceIntro("Greetings", "!"); // "Greetings, I'm Alice!"For a complete guide to these three methods, see js bind, call, and apply methods full tutorial.
Rule 4: new Binding
When a function is called with new, this is the newly created object:
function Timer(label) {
this.label = label;
this.elapsed = 0;
// this = the fresh object created by new
}
Timer.prototype.tick = function() { this.elapsed += 1; };
const t = new Timer("stopwatch");
console.log(t.label); // "stopwatch"
console.log(t.elapsed); // 0The new binding overrides all other rules except arrow functions.
Rule Priority
When multiple binding rules could apply, the priority is:
| Priority | Rule | Example |
|---|---|---|
| 1 (highest) | new binding | new Foo() |
| 2 | Explicit binding | foo.call(ctx) / foo.bind(ctx)() |
| 3 | Implicit binding | obj.foo() |
| 4 (lowest) | Default binding | foo() |
function greet() { return this.name; }
const obj = { name: "A", greet };
// Implicit says this = obj (rule 3)
// But explicit overrides:
greet.call({ name: "B" }); // "B" — explicit wins over implicitthis in Arrow Functions
Arrow functions are the exception to all four rules — they capture this from their enclosing lexical scope at definition time and cannot be rebound:
const team = {
name: "DevTeam",
members: ["Alice", "Bob"],
listMembers() {
// Arrow function captures 'this' from listMembers() — which is team
return this.members.map(m => `${this.name}: ${m}`);
},
};
console.log(team.listMembers());
// ["DevTeam: Alice", "DevTeam: Bob"]
// Calling bind/call/apply on an arrow function has no effect on this
const arrow = () => this;
arrow.call({ x: 1 }); // Still the original captured thiscall, apply, and bind cannot change this for arrow functions. For the full story, see how arrow functions change this in JavaScript.
this in Class Methods
Inside a class, this behaves as implicit binding — it refers to the instance when called as a method:
class Counter {
count = 0;
increment() {
this.count += 1; // this = the Counter instance
return this;
}
decrement() {
this.count -= 1;
return this;
}
}
const c = new Counter();
c.increment().increment().decrement();
console.log(c.count); // 1But class methods still lose this when extracted as callbacks:
const c2 = new Counter();
const inc = c2.increment; // Detached!
// inc() → TypeError: Cannot set properties of undefined
// Fix: use bind, arrow field, or arrow wrapperthis in Event Handlers and Callbacks
The most common pain point — this in callbacks:
class Button {
constructor(label) {
this.label = label;
this.clicks = 0;
}
// BAD: this will be undefined (strict) or the event target (DOM)
handleClickBad() {
this.clicks++;
}
// GOOD option 1: bind in constructor
constructor2(label) {
this.label = label;
this.clicks = 0;
this.handleClick = this.handleClick.bind(this);
}
// GOOD option 2: class field arrow function
handleClick = () => {
this.clicks++; // Arrow captures this from class body (the instance)
console.log(`${this.label} clicked ${this.clicks} times`);
};
}
const btn = new Button("Submit");
setTimeout(btn.handleClick, 100); // Works: arrow field keeps thisQuick Reference: this by Call Pattern
function fn() { return this; }
const obj = { fn };
fn(); // globalThis or undefined (strict)
obj.fn(); // obj
fn.call({ x: 1 }); // { x: 1 }
fn.apply({ x: 2 }); // { x: 2 }
const bound = fn.bind({ x: 3 }); bound(); // { x: 3 }
new fn(); // {} (new object)
const arrow = () => this; // Lexical — whatever this was at definitionRune AI
Key Insights
- this is set at call time, not definition time: The same function can have different this values depending on how it is invoked — which object is to the left of the dot or which binding method is used
- Four binding rules in priority order: new > explicit (call/apply/bind) > implicit (obj.method()) > default (bare call)
- Implicit binding is easily lost: Assigning a method to a variable or passing it as a callback detaches it from its object — this becomes undefined (strict) or globalThis
- Arrow functions capture this lexically: Defined at creation time from the enclosing scope; cannot be overridden by call, apply, or bind
- Class field arrow functions solve callback binding: Declaring methods as arrow field properties (handleClick = () => { ... }) ensures this is always the instance
Frequently Asked Questions
Why does this work differently from other languages?
How do I pass this safely to an async operation?
Does this work the same in Node.js modules?
Can I reliably determine this at read time?
Conclusion
JavaScript this is determined by four rules, applied in priority order: new binding overrides everything; explicit binding (call/apply/bind) comes next; implicit binding (method call on an object) comes third; and default binding (bare function call) has the lowest priority. Arrow functions bypass all four rules — they capture this from their enclosing lexical scope at definition time. Mastering these rules eliminates the most common category of JavaScript bugs.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.