DEV Community

Cover image for Find what JavaScript variables are leaking into the global scope
Matteo Mazzarolo
Matteo Mazzarolo

Posted on • Originally published at mmazzarolo.com on

Find what JavaScript variables are leaking into the global scope

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! — 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 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 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>
  <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>
Enter fullscreen mode Exit fullscreen mode

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

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:

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)
Enter fullscreen mode Exit fullscreen mode

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.
(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);
})();
Enter fullscreen mode Exit fullscreen mode

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

runtime globals snippet

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

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 instead.

Top comments (0)