Find what JavaScript variables are leaking into the global scope
Tags: javascript, webdev, tutorial, frontend
Published: 2022-02-22 15:13:12

---
title: Find what JavaScript variables are leaking into the global scope
published: true
date: 2022-02-22 15:13:00 UTC
tags: #javascript #webdev #tutorial #frontend
canonical_url: https://mmazzarolo.com/blog/2022-02-14-find-what-javascript-variables-are-leaking-into-the-global-scope/
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wxmsqhk0fwe3glqryoyj.png
---

Detecting global variable leaks can be helpful to debug your apps and avoid collisions in the global scope. The more a web app grows, the more having a good understanding of what’s happening in the global scope becomes important (e.g., to ensure multiple libraries — [or even multiple apps!](https://micro-frontends.org/) — can coexist on the page without collisions).

In this post, I’ll show you how to find variables that have been added or leaked into the global scope at runtime in web apps (thanks to [@DevelopSean](https://twitter.com/DevelopSean) for introducing me to this trick at InVision).

> **Disclaimer** : in this post, I’ll reference global scope using the `window` property. Please notice that, in most cases, the [`globalThis`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis) property would be a better candidate to access the global object since it works in different JavaScript environments. That said, this post is specific to web (non-worker) contexts, so I think using the `window` term here makes it easier to follow.

* * *

Let’s say you want to check what global variables are being added to the `window` object on this page (with some code that looks bad on purpose):

```html
<html>
  <body>
    <h1>Hello world!</h1>
    <script src="https://unpkg.com/jquery@3.6.0/dist/jquery.js"></script>
    <script>
      function doSomethingTwice() {
        for (i = 0; i <= 2; i++) {
          const myString = `hello-world-${i}`;
          // Let's imagine we're going to do something with myString here...
        }
      }
      doSomethingTwice();
    </script>
  </body>
</html>
```

Typically, you’d probably open the DevTools console and inspect the `window` object looking for suspicious variables.

![too many globals](https://mmazzarolo.com/static/fab9d11e2a16ef47f2a5d12e123dbf25/1e043/too-many-globals.png "too many globals")

This approach can work, but… it’s a lot of work. The browser and the JavaScript engine themselves add a bunch of globals on the `window` object (e.g., JavaScript APIs such as `localStorage`, etc.), so finding globals introduced by our code is like looking for a needle in a haystack.

One possible way to get around this issue is to grab a list of all the default globals and filter them out from the `window` object by running a similar snippet in the DevTools console:

```js
const browserGlobals = ['window', 'self', 'document', 'name', 'location', 'customElements', 'history', 'locationbar', 'menubar', 'personalbar', 'scrollbars', 'statusbar', 'toolbar', 'status', 'closed', 'frames', 'length', 'top', ...];

const runtimeGlobals = Object.keys(window).filter(key => {
  const isFromBrowser = browserGlobals.includes(key);
  return !isFromBrowser;
});

console.log("Runtime globals", runtimeGlobals)
```

Doing so should work, but it leaves two open questions:

- How do you get the `browserGlobals` variables?
- Between cross-browser differences and JavaScript API updates, maintaining the `browserGlobals` list can quickly get hairy. Can we make it better?

To answer both questions, we can generate the `browserGlobals` list programmatically by populating it with the globals of a pristine `window` object.  
There are a couple of ways to do it, but to me, the cleanest approach is to:

1. Create a disposable iframe pointing it at `about:blank` (to ensure the `window` object is in a clean state).
2. Inspect the iframe `window` object and store its global variable names.
3. Remove the iframe.

```js
(function () {
  // Grab browser's default global variables.
  const iframe = window.document.createElement("iframe");
  iframe.src = "about:blank";
  window.document.body.appendChild(iframe);
  const browserGlobals = Object.keys(iframe.contentWindow);
  window.document.body.removeChild(iframe);

  // Get the global variables added at runtime by filtering out the browser's
  // default global variables from the current window object.
  const runtimeGlobals = Object.keys(window).filter((key) => {
    const isFromBrowser = browserGlobals.includes(key);
    return !isFromBrowser;
  });

  console.log("Runtime globals", runtimeGlobals);
})();
```

Run the snippet above in the console, and you’ll finally see a clean list with of the runtime variables 👍

![runtime globals snippet](https://mmazzarolo.com/static/d0118bb0ac9c42fd0afd75f74cde0cc1/1e043/runtime-globals-snippet.png "runtime globals snippet")

For a more complex version of the script, I created this Gist:

{% embed https://gist.github.com/mmazzarolo/4cc6ab1b3f176874336bf1ceb7dff6ef %}

A couple of final notes:

- This utility can easily run in a Continuous Integration context (e.g., in E2E tests using Cypress) to provide automated feedback.
- I recommend running this utility in browser tabs with no extensions: most browser extensions inject global variables in the `window` object, adding noise to the result (e.g., ` __REACT_DEVTOOLS_BROWSER_THEME__ `, etc. from the React DevTools extension).
- To avoid repeatedly copy/pasting the global checker code in your DevTools console, you can create a [JavaScript snippet](https://developer.chrome.com/docs/devtools/javascript/snippets/) instead.