Global Object vs. Global Declarations
Version: ES2015 (let, const, class), ES2020 (globalThis)
Level: Intermediate
What passes as “global” in JavaScript can be quite complicated. Internally, it’s defined by something known as an environment record that is made up of multiple other components. To simplify things here, we’re going to reduce those components to just two: the global object and global declarations.
At the root of it all is the global object, seen in browsers (more or less) as window
, global
on Node, and now more consistently represented everywhere as globalThis
. When you refer to a variable that is not defined in any other scope but exists in the global object, you will get the value of the property of that name in the global object.
globalThis.myProp = 'value';
console.log(myProp); // value
Some declarations in the top level global scope also create global properties on the global object. These include var
and function
declarations.
var myVar = 'value';
function myFunc () {}
console.log('globalVar' in globalThis); // true
console.log('func' in globalThis); // true
Other declarations, specifically those introduced in ES2015 such as let
, const
, and class
, do not create properties on the global object.
let myLet = 'value';
const myConst = 'value';
class MyClass {}
console.log('myLet' in globalThis); // false
console.log('myConst' in globalThis); // false
console.log('MyClass' in globalThis); // false
While these declarations are not available in the global object, they are still global. Instead of being global object properties, they’re part of the global declarations component of global. Global declarations are global identifiers that are accessible everywhere (being global) but do not exist in the global object.
Because var
and functions
are also declarations, they can still impact usage of the new ES2015 declarations despite being tied to the global object. For example if you try to declare a global let
after a global var
, or vice versa, you’ll get an error.
let myFirstValue = 'value';
var myFirstValue = 'value'; // SyntaxError
var mySecondValue = 'value';
let mySecondValue = 'value'; // SyntaxError
However, if you create a global as a global property, this does not conflict with global declarations, old or new.
globalThis.myFirstValue = 'value';
var myFirstValue = 'value'; // Ok
globalThis.mySecondValue = 'value';
let mySecondValue = 'value'; // Ok
Because var
and function
also set global properties, their declarations would overwrite (assign) any existing global object property of the same name (myFirstValue
above). The lexical declarations of let
, const
, and class
will not, effectively giving you two global values of the same name, one as a declaration, and one as a global object (mySecondValue
). Unqualified references to global variables by name only will use the declaration version if it exists, otherwise falling back to the global object property. Qualified references going through the global object will give you the global object property ignoring any respective declaration.
globalThis.myFirstValue = 'prop';
var myFirstValue = 'var';
console.log(globalThis.myFirstValue); // 'var'
console.log(myFirstValue); // 'var'
globalThis.mySecondValue = 'prop';
let mySecondValue = 'let';
console.log(globalThis.mySecondValue); // 'prop'
console.log(mySecondValue); // 'let'
The new lexical declaration behavior of let
, const
, and class
can be important in helping to prevent conflicts with global properties, especially in environments like the browser where the global object is not only global, but also a window object containing all the properties and methods for the current browser window. For example, if you want to create a name
variable in global, before let
or const
, you’d have a conflict with the window.name
property.
var name = {};
console.log(name); // [object Object]
// (converted to string for the browser window's name)
Instead, using a lexical declaration with let
or const
means the window’s name
property will be ignored since its only a global property and not a declaration.
let name = {};
console.log(name); // {}
console.log(window.name); // '' (or whatever the window is named)
All of the global built-ins are properties only, so in using a lexical declaration with your own properties, you’ll help ensure that you won’t see any conflicts with existing built-ins.
More info: