JS Tip of the Day: Global Object vs. Global Declarations

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: