Written by Faraz Kelhini✏️
The JavaScript language is increasingly used in a wide variety of environments. In addition to the web browser, which is the most common type of host environment for JavaScript, you can run JavaScript programs in servers, smartphones, and even robotic hardware.
Each environment has its own object model and provides a different syntax to access the global object. In the web browser, for example, the global object is accessible via window
, self
, or frames
. In Node.js, however, these properties don’t exist, and you must use global
instead. In Web Workers, only self
is available.
These different ways of referencing the global object have made it tough to write a portable JavaScript code that works in multiple environments. Fortunately, there’s a proposal in the works that aims to fix this issue by introducing a standard property called globalThis
that will be available in all environments.
In this article, we’ll first look at the global object in popular JavaScript environments and then see how globalThis
provides a unified mechanism to access it.
window
The window
property is used to refer to the global object of the current document in the browser environment. At the top level of the code, variables declared using the var
keyword become properties of window
and are accessible from anywhere in the code:
var a = [10, 20];
console.log(window.a); // → [10, 20]
console.log(a === window.a); // → true
Normally, it’s not necessary to directly refer to window
when using its properties because the reference is implied. However, when there is a local variable with the same name as the global variable, using window
is the only option:
var a = 10;
(function() {
var a = 20;
console.log(a); // → 20
console.log(window.a); // → 10
})();
As you can see, window
is very useful for referencing the global object, regardless of the scope in which the code is running. Note that window
actually references window.window
. So, window.window === window
.
Besides the standard JavaScript properties and methods, the window
object contains several additional properties and methods that allow us to control the web browser window as well as the document itself.
self
The Web Workers API doesn’t have a Window
object because it has no browsing context. Instead, it provides the WorkerGlobalScope
interface containing the data that’s normally carried by window
.
To access the global object in Web Workers, we use self
, which is a synonym for the window
property of the Window
object. Similar to window
, self
is a reference to the global object and can be used to make references explicit rather than implicit:
// a web worker
console.log(self); // => DedicatedWorkerGlobalScope {...}
var a = 10;
console.log(self.a); // → 10
console.log(a === self.a); // → true
In a browser environment, this code would log Window
rather than DedicatedWorkerGlobalScope
. Because self
‘s value changes depending on the environment in which it is used, it’s sometimes preferable to window
. While self
references WorkerGlobalScope.self
in the web worker context, it references window.self
in the browser context.
It’s important not to confuse the self
property with the common JavaScript pattern of declaring a local variable, which is used to maintain a reference to a context. For instance:
const obj = {
myProperty: 10,
myMethod: function(){
console.log(this === obj); // => true
// store the value of this in a variable for use in nested functions
const self = this;
const helperFunction = (function() {
console.log(self === obj); // => true (self refers to the outer this value)
console.log(this === obj); // => false (this refers to the global object. In strict mode, it has a value of undefined)
})();
}
};
// invoke myMethod on the object obj.
obj.myMethod();
frames
Another way to access the global object in the browser environment is to use the frames
property, which works similar to self
and window
:
// browser environment
console.log(frames); // => Window {...}
This read-only property is usually used to obtain a list of sub-frames of the current window. For example, you can use window.frames[0]
or frames[0]
to access the first frame.
global
In Node.js, you can access the global object using the global
keyword:
// node environment
console.log(global); // => Object [global] {...}
window
, self
, or frames
won’t work in the Node environment. Keep in mind that the top-level scope in Node.js is not the global scope. In browsers, var abc = 123
will create a global variable. In Node.js, however, the variable will be local to the module itself.
this
In browsers, you can use the this
keyword at the top level of your program to reference the global object:
this.foo = 123;
console.log(this.foo === window.foo); // => true
this
inside functions running in non-strict mode or arrow functions also references the global object. But that’s not the case with functions running in strict mode, in which this
has a value of undefined
:
(function() {
console.log(this); // => Window {...}
})();
(() => {
console.log(this); // => Window {...}
})();
(function() {
"use strict";
console.log(this); // => undefined
})();
In Node modules, this
at the top level doesn’t reference the global object. Instead, it has the same value as module.exports
. Inside functions (Node environment), the value of this
is determined based on how the function is called. In JavaScript modules, this
at the top level is undefined
.
Introducing globalThis
globalThis
aims to consolidate the increasingly fragmented ways of accessing the global object by defining a standard global property. The globalThis
proposal is currently at stage 4, which means it’s ready for inclusion in the ES2020 standard. All popular browsers, including Chrome 71+, Firefox 65+, and Safari 12.1+, already support the feature. You can also use it in Node.js 12+.
// browser environment
console.log(globalThis); // => Window {...}
// node.js environment
console.log(globalThis); // => Object [global] {...}
// web worker environment
console.log(globalThis); // => DedicatedWorkerGlobalScope {...}
By using globalThis
, your code will work in window and non-window contexts without having to write additional checks or tests. In most environments, globalThis
directly refers to the global object of that environment. In browsers, however, a proxy is used internally to take into account iframe and cross-window security. In practice, it doesn’t change the way you write your code, though.
Generally, when you’re not sure in what environment your code will be used, or when you want to make your code executable in different environments, the globalThis
property comes in very handy. You’ll have to use a polyfill to implement the feature on older browsers that do not support it, however.
On the other hand, if you’re certain what environment your code is going to be used in, then use one of the existing ways of referencing the environment’s global object and save yourself from the need to include a polyfill for globalThis
.
Creating a globalThis
polyfill
Prior to the introduction of globalThis
, a common way to access the global object across different environments was to use the following pattern:
function getGlobalObject() {
return Function('return this')();
}
if (typeof getGlobalObject().Promise.allSettled !== 'function') {
// the Promise.allSettled() method is not available in this environment
}
The problem with this code is that the Function
constructor and eval
cannot be used in websites that enforce Content Security Policy (CSP). Chrome’s extension system also doesn’t allow such code to run because of CSP.
Another pattern to reference the global object is as follows:
function getGlobalObject() {
if (typeof globalThis !== 'undefined') { return globalThis; }
if (typeof self !== 'undefined') { return self; }
if (typeof window !== 'undefined') { return window; }
if (typeof global !== 'undefined') { return global; }
throw new Error('cannot find the global object');
};
if (typeof getGlobalObject().Promise.allSettled !== 'function') {
// the Promise.allSettled() method is not available in this environment
}
This pattern is commonly used on the web. But this too has several flaws, making it unreliable in certain situations. Fortunately, Mathias Bynens at Chrome DevTools has come up with a creative pattern that doesn’t suffer from those shortcomings:
(function() {
if (typeof globalThis === 'object') return;
Object.defineProperty(Object.prototype, '__magic__', {
get: function() {
return this;
},
configurable: true // This makes it possible to `delete` the getter later.
});
__magic__.globalThis = __magic__; // lolwat
delete Object.prototype.__magic__;
}());
// Your code can use `globalThis` now.
console.log(globalThis);
This polyfill is a more robust solution compared to other approaches, but it’s still not perfect. Modifying Object
, Object.defineProperty
, or Object.prototype. __defineGetter__
could break the polyfill, as Mathias mentions.
Wrapping up
It’s difficult to write a portable JavaScript code that works in multiple environments. Each host environment has a slightly different object model. As a result, to access the global object, you need to use different syntax in different JavaScript environments.
With the introduction of the globalThis
property, accessing the global object will become much simpler, and it will no longer be necessary to detect the environment in which the code is running.
At first glance, globalThis
seems like an easy thing to polyfill; but in practice, it’s very complicated to do correctly. All existing workarounds are imperfect and might introduce bugs if you’re not careful.
ECMAScript is quickly evolving, and you can expect new features to be introduced more often. To get updated on the latest additions to the specs, check out the list of finished proposals.
Implementing New JS Features? Understand How JavaScript Errors Affect Your Users.
Tracking down the cause of a production JavaScript exception or error is time consuming and frustrating. If you’re interested in monitoring JavaScript errors and seeing how they affect users, try LogRocket.https://logrocket.com/signup/
LogRocket is like a DVR for web apps, recording literally everything that happens on your site.LogRocket enables you to aggregate and report on errors to see how frequent they occur and how much of your user base they affect. You can easily replay specific user sessions where an error took place to see what a user did that led to the bug.
LogRocket instruments your app to record requests/responses with headers + bodies along with contextual information about the user to get a full picture of an issue. It also records the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.
Enhance your JavaScript error monitoring capabilities – Start monitoring for free.
The post What is globalThis, and why should you start using it? appeared first on LogRocket Blog.
Top comments (0)