skip to content

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?

level: middleimportance: must knowfreq 78%

answer

  1. call site, not definition site
  2. four call forms, one order
  3. new, then explicit, then dot
  4. bare call falls through to default
  5. strictness decides the default value

basics

~10 s

JavaScript 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 s

For 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
javascript
// 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 label

go deeper

for a junior

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.

for a middle

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.

for a senior

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.

for a principal

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

context