The global object is an ordinary object created per realm that supplies the global scope with its built-in bindings (Array, Math, undefined, …) and any properties added later. globalThis is a property of it that points back to the global this value. Hosts used to name it differently (window and self in browsers, global in Node); globalThis works in all of them.
Which top-level declarations end up on it depends on the declaration and the kind of code. The spec's Global Environment Record has two halves: an Object record backed by the global object, and a Declarative record.
| Declaration | In a classic script | In a module |
|---|---|---|
var, function declaration |
Global object property (non-configurable) | Module scope only |
let, const, class |
Declarative record: a global binding, no property | Module scope only |
| Assignment to an undeclared name (sloppy) | Creates a configurable property | ReferenceError (strict) |
A script's var and function names live in the Object half, so globalThis.name and bare name are the same binding. let/const/class live in the Declarative half, which is searched first, so they can shadow a global property (but not a non-configurable one: let undefined is a SyntaxError). Two scripts that both declare the same top-level let collide with a SyntaxError, because the Declarative record is shared across all scripts in the realm.
Module code has its own Module Environment Record under the global one, so it sees globals but declares nothing in them. Node's CommonJS files are wrapped in a function, so var there isn't global either.
Use globalThis for feature detection (typeof globalThis.fetch) and polyfills. Avoid creating globals; they're shared mutable state.