Have a Question?

If you have any question you can ask below or enter what you are looking for!

Question Detial

Background

I just learned that calling keys(window) (or Object.keys(window)) in the DevTools console reveals variables in the global scope (source). I called that code on the StackOverflow page and got this result:

StackOverflow page global variables

Variable i got my attention as it seems to be in the global scope by a mistake. I tried locating code that is responsible for declaring i, but it turned out to be cumbersome (there is a lot of code and a lot of is).

Question

Getting console warnings that say

Global variable "i" created (main.js:342)

could be useful. How can I implement that feature?

Research

I figured that I need some kind of an event whenever new variable is created.

  • We do have setters in JavaScript. However, creating a setter requires that you provide a property name. Since I want to monitor all properties I can't really use that.
  • __noSuchMethod__ (MDN) would be perfect but it only covers methods (and there is no __noSuchProperty__ method).
  • Object.observe (HTML5 Rocks) doesn't reveal anything about the code that created the property (console.trace() gives me only the name of the observer function).
  • Object.prototype.watch (MDN) - same as the setter, you have to specify a property name.
  • Calling Object.preventExtensions(window) (MDN) causes errors with a nice stack trace whenever new global variable is created. The problem with this solution is that it interferes with the script execution and may change its behaviour. It also doesn't allow me to catch the error and format it properly.

Notes

  • I know about jshint/jslint and I still think that catching these declarations in the runtime could be useful.
  • I don't care about i variable on the SO page that much, you can probably find the declaration using setters. My question concerns the general solution for this problem.

Answers

Jon Skeet

5:15pm 16th January 2014

IMO you have two decent options:

  1. JSHint.
  2. Strict mode.

Both will yell at you when you leak a global. Strict mode will probably be better for your usecases.

Wiktor Stribiżew

4:46pm 16th January 2014

You've definitely done your homework, and you thought of all the things I would have thought of and, as you discovered, none of them fit.

The only way I can think of doing is to just monitor the global object (this example is using window as the global object: modify accordingly for Node or another JavaScript container). Here's an example that monitors new globals and deleted globals (if you don't need to monitor deletions, you can remove that functionality):

var globals = Object.keys(window);
var monitorGlobalInterval = 50;

setInterval(function(){
    var globalsNow = Object.keys(window);
    var newGlobals = globalsNow.filter(function(key){
        return globals.indexOf(key)===-1;
    });
    var deletedGlobals = globals.filter(function(key){
        return globalsNow.indexOf(key)===-1;
    });
    newGlobals.forEach(function(key){
        console.log('new global: ' + key);
    });
    deletedGlobals.forEach(function(key){
        console.log('global deleted: ' + key);
    });
    globals = globalsNow;
}, monitorGlobalInterval);

See it in action here: http://jsfiddle.net/dRjP9/2/

Marc Gravell

3:00pm 17th January 2014

You can try this method to get a list of global variables that you've created:

(function(){
  var differences = {},
    ignoreList = (prompt('Ignore filter (comma sep)?', 'jQuery, Backbone, _, $').split(/,\s?/) || []),
    iframe = document.createElement('iframe'),
    count = 0; ignoreList.push('prop');

  for (prop in window) {
    differences[prop] = {
      type: typeof window[prop],
      val: window[prop]
    }; count++;
  }

  iframe.src = 'about:blank';
  iframe.style.display = 'none';
  document.body.appendChild(iframe);
  iframe = iframe.contentWindow || iframe.contentDocument;

  for (prop in differences) {
    if (prop in iframe || ignoreList.indexOf(prop) >= 0) {
      delete differences[prop];
      count--;
    }
  }

  console.info('Total globals: %d', count);
  return differences;
})();