When a JavaScript method is detached and then called as a plain function, this is undefined in some code and globalThis in other code. What decides which, and why is the globalThis outcome the more dangerous bug?
answer
- default binding, no receiver at the call
- mode of the callee, not the caller
- classes and modules are strict already
- sloppy substitutes the global object
- silent NaN beats no stack trace
basics
~20 sStrict mode decides it. In strict code a plain call leaves this as undefined, so the first property access throws immediately; in sloppy code the engine substitutes globalThis, so the method silently reads and writes global properties instead of failing.
solid answer
~40 sThe default binding — the receiver used when a call has no base object — is `undefined` under strict mode and `globalThis` under sloppy mode. Strictness comes from the enclosing code, not from the call: class bodies are always strict, every ES module is always strict, and a `'use strict'` directive makes a script or function strict. Only a classic non-module script without the directive still gets the `globalThis` substitution. That case is the dangerous one because nothing throws: `this.count++` quietly creates or corrupts `globalThis.count`, the real object never updates, and the symptom shows up far from the cause — often as two "instances" trampling each other's state. The strict outcome fails loudly at the first property access, which is why the same bug is much easier to find in modern module code.
code
javascript · 17 lines// Save as a .js file and run as a CLASSIC script (not a module) to see sloppy mode.
var cart = { total: 0, add(n) { this.total += n; } };
var add = cart.add;
add(5);
console.log(cart.total); // 0 — the object was never touched
console.log(globalThis.total); // NaN — undefined + 5, written globally
// Same shape inside a class body, which is always strict:
class Cart {
total = 0;
add(n) { this.total += n; }
}
const c = new Cart();
const boom = c.add;
try { boom(5); } catch (e) { console.log(e.constructor.name); } // 'TypeError'go deeper
Remember that a detached method gets no object of its own, and that modern class or module code fails fast with a TypeError. Recognise that error message as the signature of a lost receiver.
Explain the default binding precisely: strict keeps undefined, sloppy substitutes the global object and boxes primitives, and strictness is a property of the callee fixed at definition — class bodies and module bodies are strict automatically.
Argue about failure modes: silent global writes make two instances appear to corrupt each other and surface far from the cause. Show how you would add a receiver assertion or reproduce the detachment to confirm the diagnosis quickly.
Own the policy angle — enforcing modules and classes across a codebase changes this whole class of bug from silent to loud, and that is a deliberate reliability decision with a migration cost attached to any remaining classic scripts.
## Where the two values come from When a function is invoked without a receiver — `fn()` rather than `obj.fn()` — the engine still has to bind something to `this`. The rule sits in the function-call machinery: if the function is strict, the supplied `this` is used exactly as given, including `undefined`; if the function is sloppy, `undefined` and `null` are replaced by the global object, and a primitive receiver is boxed into its wrapper object. ```js function sloppy() { return this; } 'use strict'; // (not a directive here — just prose) sloppy(); // globalThis in a classic script ``` ```js 'use strict'; function strictFn() { return this; } strictFn(); // undefined ``` Crucially, the mode that matters is the mode of the **function being called**, fixed when it was defined — not the mode of the code doing the calling. A sloppy function called from strict code still gets `globalThis`. ## What is strict without you asking - Every ES module body, and every function defined inside one. - Every `class` body — constructors, methods, static blocks, field initialisers. - Anything under a `'use strict'` directive at the top of a script or function. So in a modern codebase — classes, `import`/`export` — detached methods almost always produce `undefined`. The `globalThis` case survives in classic scripts loaded without `type="module"`, in older bundles, and in `eval`-style or REPL contexts. ## Why globalThis is worse ```js // classic script, sloppy mode var cart = { total: 0, add(n) { this.total += n; } }; const add = cart.add; add(5); console.log(cart.total); // 0 — the object never changed console.log(globalThis.total); // NaN — undefined + 5 ``` Three properties make this expensive to debug: 1. **No exception.** Reading a missing property of an object yields `undefined` rather than throwing, so the failure is a wrong value, not a stack trace. The error surfaces later, in unrelated code that consumed the value. 2. **Shared mutable target.** Every detached method of every object writes into the same global namespace. Two instances that each "lose" their receiver end up reading and writing each other's fields, which reads like a race condition even though the language is single-threaded. 3. **Collisions with real globals.** The global object already carries host-defined properties. Writing to one can be ignored, coerced, or produce a platform-visible side effect rather than the value you stored — so the corruption is not even guaranteed to round-trip. The strict outcome, by contrast, throws a `TypeError` on the very first property access, with a stack that points into the method and a call path that leads straight to the registration site. ## The caveat that separates a good answer from a great one "Undefined or globalThis" only describes a **plain call**. Many callbacks are not plain calls: the API invokes them with a receiver of its own. A DOM event listener is invoked with the element the listener is attached to; a browser timer callback is invoked with the global object; Node's timer callbacks are invoked with a `Timeout` object. In those cases a strict method does **not** see `undefined` — strict functions use the receiver they are given, whatever it is, without coercion. So a detached handler can quietly operate on the wrong object rather than throwing, which puts you back in silent-bug territory even in strict code. A defensive check is cheap when you are diagnosing: ```js class Service { handle() { if (!(this instanceof Service)) { throw new TypeError('Service#handle called without its receiver'); } // ... } } ``` ## The takeaway Strict mode does not fix a lost receiver; it changes the failure mode from silent corruption to an immediate throw. That is a real improvement — but the fix is still to supply a receiver, by wrapping the call, by handing over a function that carries one, or by using an API that accepts a receiver argument.
- Does it matter whether the calling code is strict, or only the function being called?Only the function being called. Strictness is a property fixed when the function is defined, so a sloppy function invoked from strict code still receives `globalThis`, and a strict function invoked from sloppy code still receives `undefined`. This is why a stray legacy script can keep the silent variant alive inside an otherwise modern codebase.
- Is turning on strict mode a legitimate fix for this bug?It is a diagnosis aid, not a fix. Strict mode converts silent global corruption into a `TypeError` at the first property access, which is much cheaper to trace, but the receiver is still missing. The fix is to supply one — wrap the call, hand over a function carrying its own receiver, or use an API that takes a receiver argument.
- If code is strict, can a detached method still get something other than undefined?Yes. Strict functions use whatever receiver the caller passes, uncoerced — and callback APIs often pass one. A DOM listener is invoked with the element it is attached to, a browser timer callback with the global object. So a detached handler in strict code can silently operate on the wrong object instead of throwing.
saying these in an interview costs you the question
- Thinks the caller's strict mode decides the receiver
- Says strict mode fixes the lost receiver
- Believes the global fallback throws like the strict one does
- Claims this is always globalThis in browsers
- Assumes classes need an explicit 'use strict' directive