Global bindings are properties on the shared global object—globalThis in modern code—rather than a separate “global variable” keyword. Accidental globals from bare assignments were common in old scripts, but strict mode (and ES modules, which are strict by default) throw when you assign to an undeclared identifier, which nudges teams toward let/const, explicit exports, and deliberate globalThis namespacing. Default to explicit globalThis when you truly need a cross-script handle in the browser, keep module graphs explicit with import / export, and remember that strict mode rejects accidental globals created by assigning to an undeclared identifier. When import fails in a classic script, the fix is usually a module graph or bundler change—see cannot use import statement outside a module.
Tested on: Node.js v20.18.2. A short note after each runnable snippet describes what you should see in the console.
Put data on globalThis deliberately
globalThis is the interoperable name for the global object in browsers, Node, workers, and embedded runtimes. Assigning a property is the clearest pattern when you control the name and accept the collision risk:
globalThis.__gvArticleDemo = 2026;
console.log(globalThis.__gvArticleDemo);
Reflect.deleteProperty(globalThis, "__gvArticleDemo");You should see one line logging 2026.
In browsers you can still read window on the main thread, but globalThis reads the same object there and stays portable.
var inside a block versus let
var is function-scoped (or script-scoped), so a var inside a block still exists after the block ends. let stays block-scoped, so the same pattern throws when you leave the block:
(function () {
{
let another = 23;
var newOne = 23;
}
console.log(newOne);
try {
console.log(another);
} catch (e) {
console.log(e.name);
}
})();You should see 2 lines, in order: 23, ReferenceError.
That is why modern code prefers let / const for locals: it narrows visibility without relying on hoisting rules.
Strict mode blocks implicit globals
In strict functions—and in JavaScript modules strictness is automatic—assigning to a name that was never declared is a ReferenceError, not a silent global:
try {
(function () {
"use strict";
__gvStrictLeak = 1;
})();
} catch (e) {
console.log(e.name);
}You should see one line logging ReferenceError.
Sloppy-mode implicit globals (avoid in new code)
Without strict mode, assigning to an undeclared identifier creates a property on the global object. The snippet below uses new Function so the assignment runs in a non-strict body even when the surrounding file is strict (the same technique used in automated tests):
const g = new Function("__gvSloppyLeak = 7;");
g();
console.log(globalThis.__gvSloppyLeak);
Reflect.deleteProperty(globalThis, "__gvSloppyLeak");You should see one line logging 7.
Do not rely on this pattern in application code; it hides typos and collides across third-party scripts.
vm.runInThisContext and classic “eval” globals in Node
In Node, a normal .cjs file wraps top-level declarations in a module function, so a top-level var in that file is not automatically a globalThis property (unlike a browser <script> without type="module"). Legacy code that expected true globals sometimes used vm.runInThisContext, which executes a string like a script body on the current globalThis:
const vm = require("vm");
vm.runInThisContext("var __gvVmVar = 123");
console.log(globalThis.__gvVmVar);
Reflect.deleteProperty(globalThis, "__gvVmVar");You should see one line logging 123.
Prefer export / import or explicit globalThis.myNamespace = {...} over scattering var through eval-like paths.
let at the top level is not a globalThis own property
Top-level let / const create module or script bindings; they do not define enumerable properties on globalThis the way var does in a sloppy browser script:
const hasZ = new Function(`
"use strict";
let z = 1;
return Object.prototype.hasOwnProperty.call(globalThis, "z");
`)();
console.log(hasZ);You should see one line logging false.
javascript global variable across files
Across files you have three sane options:
- ES modules: declare
export const api = {...}in one file andimport { api } from "./api.js"elsewhere. Nothing is “global” except the module namespace object the loader gives you. - Bundlers still resolve to modules; avoid leaking to
windowunless you intentionally support a plugin host page. - When you must bridge legacy scripts, attach one namespace object:
globalThis.MyApp = globalThis.MyApp || {};and hang helpers from it, instead of dozens of loose identifiers.
Why globals are risky
Shared globals are easy targets for name collisions between your code, analytics snippets, and extensions; they complicate testing because every test shares the same mutable bag; and they hide data flow. Reach for globals only at integration boundaries (for example a single namespace on globalThis), not as your default state store.
Summary
Javascript global variable confusion usually splits into browser sloppy-mode var hoisting, ES module top-level bindings that are not globalThis properties, and intentional globalThis namespaces for legacy bridges. The recurring FAQ is whether let at the top level shows up on window—in modern browsers it does not, which is why feature detection should use globalThis or window explicitly when you mean the real global object.
Across files, prefer modules (import / export) or bundler equivalents instead of scattering identifiers; when you must share state, attach one well-named object to globalThis rather than dozens of loose globals that collide with analytics snippets or user extensions. Understanding strict versus sloppy scripts still matters when you maintain older <script> tags without type="module".
