In JavaScript, how does the engine decide what `this` refers to inside a regular (non-arrow) function, and in what order do the binding rules apply when more than one could?
answer
- call site, not definition site
- four call forms, one order
- new, then explicit, then dot
- bare call falls through to default
- strictness decides the default value
basics
~10 sJavaScript resolves this at the call site, using four rules in priority order: new binding, explicit call/apply/bind, the object before the dot, then the default — globalThis in sloppy mode, undefined in strict mode.
solid answer
~50 sFor a regular function, `this` is not fixed when the function is written — it is decided every time the function is invoked, from the shape of the call site. There are four rules and they have a strict priority order. First, if the call uses `new`, `this` is the brand-new object being constructed. Second, if the call supplies a receiver explicitly — `f.call(obj)`, `f.apply(obj)`, or a function produced by `f.bind(obj)` — that receiver wins. Third, if the call is a property access, `obj.f()`, `this` is the object immediately to the left of the dot. Fourth, if none of those apply — a bare `f()` — default binding applies: `this` is `globalThis` in sloppy mode and `undefined` in strict mode, which includes class bodies and ES modules. Arrow functions sit outside this list entirely; they have no own `this`.
code
javascript · 11 lines// Run as a classic script (sloppy mode).
function report() {
console.log(this === globalThis ? 'default' : this.label);
}
const o = { label: 'implicit', report };
o.report(); // 'implicit' -> object before the dot
report.call({ label: 'explicit' }); // 'explicit' -> explicit receiver
report(); // 'default' -> no receiver at all
new report(); // undefined -> the new object has no labelgo deeper
Be able to say that this depends on how a function is called, and read a simple call site: obj.fn() gives obj, a bare fn() gives the default.
Name all four rules in priority order and explain what the default is in sloppy versus strict mode. Expect a snippet where the same function is invoked several ways and you must give each result.
Show that you diagnose by reading call sites rather than guessing, and explain why strict-mode defaults turn silent misbinding into an immediate TypeError in real services.
Be ready to argue for codebase-wide conventions that make binding unambiguous — where APIs should accept a receiver, and why relying on implicit binding across module boundaries creates fragile contracts.
## The one sentence that unlocks everything In JavaScript, a regular function's `this` is **not** part of the function. It is not captured when the function is written, it is not a property of the object the function happens to live on, and it is not the function itself. It is an extra, invisible argument that the engine computes **at each call, from the shape of the call site**. The same function object can therefore see four different `this` values in four consecutive lines. So the whole skill is: look at how the function is *called*, not where it was *defined*, and apply four rules in order. ## Rule 1 — new binding (highest priority) If the call site is `new F(...)`, `this` inside `F` is the freshly created object, and — unless the constructor explicitly returns some other object — that object is also the result of the expression. ```js function Point(x) { this.x = x; } const p = new Point(3); p.x; // 3 — this was the new object ``` Because this rule sits at the top, it beats anything else the call site could have said about the receiver. ## Rule 2 — explicit binding If the call passes a receiver explicitly, that receiver wins over anything the syntax implies: ```js function who() { return this.tag; } const a = { tag: 'a', who }; a.who(); // 'a' — implicit a.who.call({ tag: 'x' }); // 'x' — explicit beats implicit ``` One detail worth knowing: what you pass is not always what you get. In a **sloppy-mode** function, a `null` or `undefined` receiver is replaced by `globalThis`, and a primitive receiver is boxed into a wrapper object. In a **strict-mode** function the value is passed through untouched, so `this` really can be `null`, `7`, or `'hi'`. ## Rule 3 — implicit binding (the object before the dot) If the call is a property access — `obj.f()`, `obj['f']()`, `arr[0].f()` — then `this` is the object that the property was read from. Formally, the call target evaluates to a Reference whose *base* is that object, and the engine passes the base as `this`. ```js const counter = { n: 0, inc() { this.n++; } }; counter.inc(); counter.n; // 1 ``` Only the **immediate** base matters. In `app.db.users.save()`, `this` is `app.db.users`; the outer containers are irrelevant. ## Rule 4 — default binding (lowest priority) A bare call — `f()`, or a function value invoked without a receiver — matches no rule above, so the default applies: - **Sloppy mode:** `this` is `globalThis`. - **Strict mode:** `this` is `undefined`. Strict mode is not exotic any more: class bodies are always strict, ES module code is always strict, and most bundled or linted code opts in. That is why the modern failure mode is `TypeError: Cannot read properties of undefined` rather than a silent write to a global. The same default catches a plain function *nested inside* a method — the enclosing method's receiver is not inherited by an inner `function`, because that inner call site has no receiver of its own. ## Reading a call site in practice Given any invocation, ask in order: 1. Is there a `new` in front? → the new object. 2. Is there a `.call(...)`, `.apply(...)`, or was this function produced by `.bind(...)`? → that receiver. 3. Is there a dot (or bracket) immediately before the parentheses? → the object left of it. 4. Otherwise → `globalThis` or `undefined`, depending on strictness. ```js function report() { console.log(this === globalThis ? 'default' : this.label); } const o = { label: 'implicit', report }; o.report(); // 'implicit' report.call({ label: 'explicit' }); // 'explicit' report(); // 'default' (sloppy-mode script) new report(); // undefined — the new object has no label ``` ## The one function shape that ignores all four Arrow functions have no own `this` at all, so none of these rules can set it — they read `this` from the enclosing scope, and even `call`/`apply` cannot change it. Treat them as outside the precedence table rather than as a fifth entry in it. ## Why interviewers like this Every real `this` bug is one of these rules firing when the author expected a different one: a method used as a value, a helper function nested in a method, a constructor invoked without `new`. Being able to name the rule that fired — instead of reaching for a fix by reflex — is what the question is testing.
- What does `this` end up as if you call a strict-mode function with `f.call(null)`?It stays `null`. Strict-mode functions receive the thisArg exactly as passed. In a sloppy-mode function the same call would substitute `globalThis` instead, and a primitive thisArg such as `7` would arrive boxed as a `Number` object rather than as the primitive.
- Does the precedence order change inside a class body?The order is identical — what changes is the default. Class bodies are always strict mode, so a class method invoked with no receiver gets `this === undefined` rather than `globalThis`, which turns a silently wrong result into an immediate TypeError.
- How would you work out which rule fired when debugging an unexpected `this`?Read the call site, not the function body. Look for `new`, then for `.call`/`.apply`/a bound function, then for a dot immediately before the parentheses; if none are present it is default binding. Logging `this` at the top of the function confirms which branch you landed in.
saying these in an interview costs you the question
- Says this refers to the function itself
- Claims this is fixed where the function is defined
- Says this always points to the global object
- Thinks an object literal owns its methods' this permanently
- Cannot name any priority order among the call forms