The Four Rules of this: Default, Implicit, Explicit & new
Master the most misunderstood keyword in JavaScript. Learn the four deterministic rules that govern 'this' binding at call time.
By the end, you’ll be able to…
- Identify the four rules of 'this' binding: Default, Implicit, Explicit, and 'new'.
- Control execution context using call(), apply(), and bind().
- Explain how arrow functions bypass these four rules via lexical inheritance.
01The Golden Rule of 'this'
The keyword this in JavaScript is legendary for generating confusion. Many developers mistakenly believe this refers to the function itself, or to the function's lexical scope. Neither is true.
The golden rule of this is simple: this is NOT author-time binding; this is CALL-SITE runtime binding. What this refers to is determined entirely by HOW a function is called, not where it was declared (with the sole exception of arrow functions, which borrow this lexically).
The value of the 'this' keyword is never fixed by the function's declaration (with the singular exception of arrow functions); it is determined dynamically by the Call-Site: the exact syntactic form used to invoke the function at runtime.
To master 'this', you only need to apply four precedence rules in order: 1) New Binding: was the function called with new? If so, this is the newly constructed object. 2) Explicit Binding: was it called with call, apply, or bind? If so, this is the explicitly specified context. 3) Implicit Binding: was it called as an object method (obj.fn())? If so, this is the context object. 4) Default Binding: if none of the above apply, this is undefined in strict mode (or window/global in sloppy mode).
02The Four Deterministic Rules
To determine what this references at any call site, evaluate these four rules in strict order of precedence: 1. new Binding: Was the function invoked with new? If so, this is the newly constructed object. 2. Explicit Binding: Was the function invoked with call(), apply(), or bind()? If so, this is the explicitly specified object. 3. Implicit Binding: Was the function invoked with a context object (obj.method())? If so, this is that context object. 4. Default Binding: If none of the above apply (a standalone function call like fn()), this is the global object (window/global), or undefined if executing in strict mode ('use strict').
Memorizing this 4-step precedence order demystifies every single this binding scenario you will ever encounter in JavaScript.
03Losing Implicit Binding: The Detached Method Trap
One of the most frequent production bugs occurs when an implicit method reference is passed as a callback: const handler = user.greet; setTimeout(handler, 1000).
When setTimeout executes handler, it invokes it as a standalone function call! The implicit binding to user is completely lost, causing this to fall back to Default Binding (undefined or window). To fix this, you must explicitly hard-bind the function using .bind(user) or wrap it in an arrow function.
04Why This Matters in Production
In enterprise web applications, UI event handlers, class method callbacks, and asynchronous promise handlers routinely crash with TypeError: Cannot read properties of undefined (reading 'setState') when developers pass detached methods without binding.
By knowing the 4 rules and the precedence order (new > explicit > implicit > default), you can instantly diagnose and resolve broken context bugs, create robust bound handlers, and leverage explicit binding for clean functional delegation.
The ideas worth keeping.
- 'this' is determined at call time by how the function is invoked, not where it was declared.
- Precedence order: new Binding > Explicit (call/apply/bind) > Implicit (obj.fn) > Default (undefined in strict mode).
- Use .bind(this) or arrow functions to prevent detached methods from losing context in callbacks.
Make it yours.
Type and test your code right here in the interactive workspace. Run your solution, check the output, and compare with the worked guide when ready.
Exercise 1: Hard-Binding Context Helper
Write a function bindContext(fn, context) that replicates the core behavior of Function.prototype.bind using a closure and apply().
Explore a worked solution
function bindContext(fn, context) { return function(...args) { return fn.apply(context, args); };}Returning a closure that invokes fn.apply(context, args) guarantees that 'this' is locked to the specified context.
Exercise 2: Fixing Detached Event Handler
Fix this class component handler so that passing this.handleClick as an event listener preserves 'this': class Button { click() { return this.label; } }
Explore a worked solution
class Button { constructor(label) { this.label = label; this.handleClick = this.click.bind(this); } click() { return this.label; }}Binding the method in the constructor guarantees that 'this' remains permanently bound to the instance, even when passed as a detached callback.
What stayed with you?
Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.
Exercise 1: Hard-Binding Context Helper
Write a function bindContext(fn, context) that replicates the core behavior of Function.prototype.bind using a closure and apply().
[Process exited with code 0 - No output]
Keep exploring.
The official references behind this lesson, when you want a second angle or the full details.
One lesson closer.
Reading is a start. Trying it yourself makes the connection.