Checking your JavaScript for variable leaks

Ah, global variables, the bain of any JavaScript developer’s working life. Especially if you’re in the business of writing a library, you want to minimise the number of global vars your code produces so as to minimise the potential for conflicts with other scripts running in the same environment. It’s all too easy to miss a var statement or to declare something in the wrong scope, so I use this little tool to check that my libraries don’t create any unexpected variables:

var Globals = {
  originals:   [],
  userDefined: [],
  warned:      [],
  root:        this,

  
  initialize: function() {
    if (this.originals.length > 0) return;
    for (var key in this.root) this.originals.push(key);
  },
  
  register: function() {
    for (var i = 0, n = arguments.length; i < n; i++)
      this.userDefined.push(arguments[i]);
  },
  
  check: function() {
    for (var key in this.root) {
      if (this.originals.indexOf(key) == -1
          && this.userDefined.indexOf(key) == -1
          && this.warned.indexOf(key) == -1) {
        console.warn('Global variable: ' + key);
        this.warned.push(key);
      }
    }
  },
  
  run: function() {
    var self = this;
    setInterval(function() { self.check() }, 1000);
  }
};

// Example -- variables from JS.Class
Globals.register('JS', 'it', 'its', 'require', 'undefined');
Globals.initialize();

Put this in an external script file and load it before loading your library’s code. Then, you can call Globals.run() in Firebug and get a nice list of warnings if you’ve accidentally created a global variable. Let’s take a quick walk through how it works.

originals stores an array of all pre-existing global variables. Most JavaScript environments have a lot of built-in global variables and you want to make sure you ignore them, since they’re not a problem. userDefined stores a list of names you’ve explicitly told Globals about, so that they can also be safely ignored. warned stores names that Globals has already warned you about, as we don’t want to generate multiple warnings, and root stores a reference to the global object, equivalent to window in web browser environments.

We call Globals.register() to tell the scanner about global variables we are knowingly creating: we don’t want to be warned about them since we created them deliberately. Put any global variable names from your library in here. Then, we call Globals.initialize(), which populates the originals array with the names of any global variables currently in existence. Make sure you call this before loading your library code, otherwise your code will be ignored by the scanner.

Finally, you can call Globals.run() whenever you like, and this kicks off the scanner. The function Globals.check is run every second, and scans all the names in the global object. If it finds a name that is not in originals or userDefined or warned, it prints a warning to the Firebug console and pushes the name onto the warned array. I always run this before I release code to the public as part of my test process.