Finding Improper JavaScript Globals
When I interview web developers, my first JavaScript question is usually the following:
What is the difference, in JavaScript, betweenx = 1
andvar x = 1
. Feel free to answer in as much or as little detail as you feel comfortable.
Most people would give an answer about how the var
keyword makes something a local variable, omitting it makes it a global variable. While I'd love to hear about scope chains, the window object, and hear the term "implied global" in an answer, that basic answer is good enough. It might not show a thorough knowledge of JavaScript, but at least it shows some level of understanding of the most common dangerous feature.
There are three basic ways to make a global variable in JavaScript. You can use var
(declared global), you can just assign to it without having declared it (implied global), or you can set a property on the window
object (window global). Here's those three:
var x = 1; // declared global y = 2; // implied global window.z = 3; // window global
Implied globals are bad because they're hard to keep track of, and their declarations aren't hoisted. I don't like window globals either, because mixing window.x
and x
is bad form. In my opinion, all globals should be declared globals. Unfortunately, JavaScript makes this really hard to maintain. There are tools like JSLint that will perform analysis of your code and help you out, but it can't do an entire code base at once, at least not easily.
I've written a tool that performs run-time analysis of your application, and finds all of the improperly declared globals (both implied and window globals). Check out a demo here.
Introducing badglobals.js
badglobals.js is a tool for finding all of the improperly declared global variables in your application. Using it is simple, but don't use it in production code (see "How It Works" below).
- Include badglobals.js in your page, before any other scripts.
- When you want to do analysis, open Firebug and run
BADGLOBALS.check()
. - You'll see a warning statement in the console for every bad global found. These contain both the name, and the value.
In addition, there are a few slightly more advanced features you might be interested in.
Exclusions
If you have some globals you don't want to be reported about (such as third-party libraries), you can easily exclude them manually. Before you run the check, just call .exclude
, like so:
BADGLOBALS.exclude("jQuery", "$", "YAHOO");
Feel free to call this method as many times as you'd like, it always adds, and never removes. By default, all browser built-ins are excluded (these are found when the script is included). Sometimes, the variable _firebug
enters after the script include, and shows in the report. You should exclude this.
Report Object
While the warnings are probably enough, badglobals.js also builds a report object, containing more information. Access it by calling BADGLOBALS.report()
. This will run .check()
if it has not run already. The report object has the following properties:
- bad: An array of the names of the bad globals found.
- good: An array of the names of the good globals found.
- skipped: An array of the names of the globals that were not checked.
And that's all there is to badglobals.js. It's really simple to use, but remarkably effective.
How It Works
badglobals.js works because of one key difference between implied/window globals and declared globals: declared globals cannot be deleted. This is because using var
causes the internal property [[DontDelete]] to be set.
var x = 1; y = 2; window.z = 3; delete x; // false delete y; // true delete z; // true x; // 1 y; // undefined z; // undefined
badglobals.js simply tries to delete every property of window (skipping the built-ins, of course). If the delete succeeds, it was declared wrong. It always puts it back, but I wouldn't trust this to run in production code, because it just seems dangerous. Here's the core section of badglobals.js:
for (prop in window) { if (window.hasOwnProperty(prop)) { if (skip.indexOf(prop) >= 0 || exclude.indexOf(prop) >= 0) { skipped.push(prop); } else { val = window[prop]; if (delete window[prop]) { console.warn("Found non-var global %o with value %o", prop, val); bad.push(prop); try { window[prop] = val; } catch (e) { console.error("Oops, there was an error putting %o back :(", prop); } } else { good.push(prop); } } } }
Browser Support
This script will not work in Internet Explorer, because I use the array indexOf
method, among other things. I also think IE doesn't exactly follow the standard when it comes to delete
, so the checks might not work. I don't consider this a problem, because this is a developer tool, not production code. You'll find the complete set of bad globals with Firefox or Chrome, so you should not need to check in Internet Explorer as well. I have not tested it in Opera, but the console
references will certainly fail.
Thanks to...
I got the idea for this tool from the excellent article on delete
by kangax, over at his blog. If you haven't read that article, you really should. The depth and quality is incredible.
Get badglobals.js
Here are the links, one more time: