Skip to main content

What is globalThis, and why should you start using it?

What Is globalThis, And Why Should You Use It?
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 windowself, 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 WorkerGlobalScopeinterface 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 windowself 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 globalkeyword:
// node environment
console.log(global);    // => Object [global] {...}
windowself, 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 ObjectObject.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.

Comments

Popular posts from this blog

4 Ways to Communicate Across Browser Tabs in Realtime

1. Local Storage Events You might have already used LocalStorage, which is accessible across Tabs within the same application origin. But do you know that it also supports events? You can use this feature to communicate across Browser Tabs, where other Tabs will receive the event once the storage is updated. For example, let’s say in one Tab, we execute the following JavaScript code. window.localStorage.setItem("loggedIn", "true"); The other Tabs which listen to the event will receive it, as shown below. window.addEventListener('storage', (event) => { if (event.storageArea != localStorage) return; if (event.key === 'loggedIn') { // Do something with event.newValue } }); 2. Broadcast Channel API The Broadcast Channel API allows communication between Tabs, Windows, Frames, Iframes, and  Web Workers . One Tab can create and post to a channel as follows. const channel = new BroadcastChannel('app-data'); channel.postMessage(data); And oth...

Certbot SSL configuration in ubuntu

  Introduction Let’s Encrypt is a Certificate Authority (CA) that provides an easy way to obtain and install free  TLS/SSL certificates , thereby enabling encrypted HTTPS on web servers. It simplifies the process by providing a software client, Certbot, that attempts to automate most (if not all) of the required steps. Currently, the entire process of obtaining and installing a certificate is fully automated on both Apache and Nginx. In this tutorial, you will use Certbot to obtain a free SSL certificate for Apache on Ubuntu 18.04 and set up your certificate to renew automatically. This tutorial will use a separate Apache virtual host file instead of the default configuration file.  We recommend  creating new Apache virtual host files for each domain because it helps to avoid common mistakes and maintains the default files as a fallback configuration. Prerequisites To follow this tutorial, you will need: One Ubuntu 18.04 server set up by following this  initial ...

Working with Node.js streams

  Introduction Streams are one of the major features that most Node.js applications rely on, especially when handling HTTP requests, reading/writing files, and making socket communications. Streams are very predictable since we can always expect data, error, and end events when using streams. This article will teach Node developers how to use streams to efficiently handle large amounts of data. This is a typical real-world challenge faced by Node developers when they have to deal with a large data source, and it may not be feasible to process this data all at once. This article will cover the following topics: Types of streams When to adopt Node.js streams Batching Composing streams in Node.js Transforming data with transform streams Piping streams Error handling Node.js streams Types of streams The following are four main types of streams in Node.js: Readable streams: The readable stream is responsible for reading data from a source file Writable streams: The writable stream is re...