skip to content

At the top level of an ES module, what is the value of `this`, and what breaks in older library code written on the assumption that top-level `this` is the global object?

level: middleimportance: should knowfreq 52%

answer

  1. not what a classic script hands you
  2. no global receiver at module top level
  3. old UMD-style wrappers break here
  4. typeof it is "undefined"

basics

~20 s

Top-level this in an ES module is undefined, not the global object. Library code that assigns its export onto this, or sniffs the environment through this, throws a TypeError or silently detects nothing once the file is loaded as a module.

solid answer

~40 s

In a module, the top-level `this` is `undefined`. The module environment is created with an undefined this-value, and since module code is strict, nothing coerces it to the global object. In classic script code the top-level `this` is the global object, which is why so much pre-module library code says `this.MyLib = MyLib` or `var root = this` at the top of the file — a UMD-style wrapper. Load that same file as a module and `this.MyLib = ...` throws a `TypeError` for setting a property on undefined, while `var root = this` quietly captures `undefined` and every later `root.something` fails. The fix is to stop routing through `this`: use `export` for the module's real API, and `globalThis` if you genuinely need the global object.

code

javascript · 14 lines
javascript
// module code
console.log(this);        // undefined
console.log(typeof this); // "undefined"

// the pre-module idiom, and why it fails here
try {
  this.MyLib = { version: 1 };
} catch (e) {
  console.log(e.constructor.name); // "TypeError"
}

// ask for the global object by name instead
globalThis.MyLib = { version: 1 };
console.log(globalThis.MyLib.version); // 1

go deeper

for a junior

Remember one fact and one fix: top-level this in a module is undefined, and when you actually need the global object you write globalThis instead of relying on this.

for a middle

Explain both causes — the module record initialises the this-value to undefined, and strict mode removes the global substitution that would otherwise mask it — and show what a plain call, a method call and an arrow each see.

for a senior

Be ready to diagnose it in a real dependency: a TypeError on a top-level assignment, or a captured undefined that fails much later. Describe the safe migration to explicit exports plus a single deliberate globalThis handoff.

for a principal

Treat ambient-global sniffing as a portability liability: code whose behaviour depends on how a host happened to load it is untestable across environments. Own the policy of a named global boundary and a plan to retire it.

## The value `this` at the top level of an ES module is `undefined`. Not the global object, not an empty object, not the module namespace. ```js // module code console.log(this); // undefined console.log(typeof this); // "undefined" ``` Two separate rules produce that. First, when the engine sets up a module's environment it initialises the module's this-value to `undefined` — a property of module records, not something you can configure. Second, module code is strict mode code, so there is no this-substitution: a receiver of `undefined` stays `undefined` instead of being replaced by the global object. Contrast the classic script: top-level `this` there is the global object, so `this === window` in a browser. ## Why old libraries care Before modules, a distributable library had to work in several environments from one file, and the universal trick was to read the ambient `this` at the top of the file to find "the global": ```js // the pre-module idiom var root = this; // the global object, in script code root.MyLib = { version: 1 }; ``` When such a file is loaded as a module, three failure shapes appear: - **Immediate TypeError.** `this.MyLib = ...` throws a TypeError for setting a property on `undefined`, and because it is at the top level the whole module fails to evaluate. - **Silent capture of undefined.** `var root = this` succeeds. The failure is deferred to the first `root.foo` access, often far away, producing a confusing stack. - **Wrong environment detection.** A UMD wrapper that branches on `typeof this` picks the wrong path. It typically falls through to whatever branch handles "no known environment", so the library initialises without registering anything and calls later fail with "undefined is not a function". CommonJS is the other data point that makes this history make sense: there, the top-level `this` is the module's exports object — a third distinct value. Three loaders, three meanings for the same expression, which is exactly why library authors stopped reading `this` at file scope. ## What `this` is elsewhere in the module Only the *top level* is special; the ordinary this-binding rules still apply inside the module. ```js function f() { return this; } f(); // undefined — strict, so no global substitution const obj = { m() { return this; } }; obj.m(); // obj — method call, unchanged const arrow = () => this; arrow(); // undefined — arrows capture the enclosing this, // which at module top level is undefined ``` The arrow case is the one that surprises people: an arrow function defined at module top level closes over `undefined`, so passing it somewhere that expects to set a receiver silently does nothing useful. ## Detecting it safely `typeof this` never throws, so `typeof this === 'undefined'` is a safe check and is exactly how some wrappers detect "I am module code". If what you actually want is the global object, ask for it by name: `globalThis` (ES2020) resolves in browsers and in Node, which removes the entire reason for the old `this` sniffing. ```js // old: guess the global from ambient this var root = this; // new: name it const root = globalThis; ``` ## How to fix the pattern Stop expressing the module's public surface through `this` at all. The API belongs in `export` declarations, and if a global side effect is genuinely required — a debug handle, a legacy plugin registry other non-module code reads — write one explicit line, `globalThis.MyLib = MyLib`, so it is greppable and can be deleted later when the last consumer moves to imports. When you inherit a file you cannot rewrite, the mechanical migration is: replace top-level `this` with `globalThis`, then check whether that global assignment is still needed at all. Nine times out of ten the consumers have already switched to `import`, and the line can go. ## Interview framing The question is testing whether you know that module semantics change two things at once — the this-value and the strictness that would otherwise paper over it. A candidate who says only "modules are strict so this is undefined" has half the story; strictness alone explains why a plain function call sees `undefined`, but the *top-level* value is undefined because the module record says so.

  • Inside a plain function declared in that module, called as `f()`, what is `this`?
    `undefined`, for a different reason: module code is strict, so a call with no receiver does not get the global object substituted in. The top-level rule and the strict-call rule happen to agree here, but they are separate mechanisms — the top-level value is fixed by the module record itself.
  • Is `typeof this` at module top level safe to evaluate, or can it throw?
    It is safe. `typeof` on the `this` expression simply reports `"undefined"`; it never throws, because `this` is always resolvable — it just holds `undefined`. That makes `typeof this === 'undefined'` a usable environment check, though `globalThis` removes most reasons to need one.
  • An arrow function is defined at module top level. What does its `this` refer to when called?
    `undefined`. Arrows have no this-binding of their own and capture the enclosing one lexically; at module top level that enclosing value is `undefined`. So an arrow used as a callback that expects a receiver will not see one, no matter how it is invoked.

saying these in an interview costs you the question

  • Says top-level this in a module is window or globalThis
  • Thinks this at module top level is the exports object
  • Assumes call or bind can change a module's top-level this
  • Believes an arrow at top level captures the global object
  • Claims typeof this throws in a module

context