This Var is My Var

Monday, August 30, 2010 | 1:34 PM

The Closure Compiler you know is very different than the Closure Compiler that’s familiar to Google engineers. The defaults at Google are much stricter. It forbids duplicate global variable declarations, performs type-checking, and restricts a number of other patterns that are common in non-Closure-style JavaScript. By default, the “Google build” of Closure Compiler is like running the "open source build" of Closure Compiler with the --warning_level VERBOSE flag.

Why? Almost every contributor to Closure Tools works on web applications. We want to be able to share code. Part of that means we should be able to fix the shared code, and integrate it into all our apps quickly so that, for example, fixes for IE9 will make it out to production within a reasonable time. To integrate those changes safely, we need to be confident that they won’t break anything. Compiler restrictions help us do that. Sometimes those restrictions are obvious...sometimes less so.

Suppose Joey writes some library code:

Joey.prototype.eatHotDogs = function(hotDogs) {
for (i = 0; i < hotDogs.length; i++) {
this.eat(hotDogs[i]);
}
};

Next summer, Nathan writes an application to feed Joey HotDogs by the boxful:

Nathan.prototype.runHotDogContest = function(player, boxes) {
for (i = 0; i < boxes.length; i++) {
player.eatHotDogs(this.unpackHotDogs(boxes[i]));
}
};

Surprisingly, when Nathan runs his code in a browser, it tries to feed Joey an infinite number of hot dogs and crashes the browser.

The problem, of course, is that JavaScript "helpfully" declares variables for you if you forgot. No one declared i, so a variable i got magicked into the global scope. When Joey originally wrote his code, he tested it and it worked fine. But when Nathan tried to use it, he found that eatHotDogs() was "resetting" the i in his loop every time he called it.

In this example, Nathan was directly calling the problematic method, and the bug was relatively easy to spot. In the general case, these bugs are "non-localized" —missing var in one file may trigger a bug in an unrelated file that hasn’t been touched in years. Or it may cause a bug years later in unrelated new code. For a library shared across many codebases, non-localized bugs can stop progress quickly because it’s so hard to know where to start looking.

In VERBOSE mode, Closure Compiler enforces that all variables must be declared with the var or function or catch keywords. Global variables may only be declared once. The Google JS style guide has naming conventions to make these collisions even less likely.

This rule has some corollaries that have a big impact on how we write JavaScript. For one, there must be a way to declare variables that are "owned" by the surrounding page, and not by the code under compilation. So Closure Compiler has externs files, which allow you to declare external variables. Conversely, external variables may not conflict with your code. If you use the default externs, you will not be able to name a variable Node because it will conflict with DOM Nodes.

Try running Closure Compiler with --warning_level=VERBOSE mode and see what you find in your code!

4 comments:

trooper said...

That'll be really helpful, I always try to enforce a strict coding discipline when dealing with JavaScript. But keep in mind that even Closure Library itself produces a lot of warnings. For example I get this with most recent version of the library:

goog/ui/controlrenderer.js:298: WARNING - Access to protected property setStateInternal of goog.ui.Control not allowed here.
[exec] control.setStateInternal(state);

:)

George Moschovitis said...

JavaScript sure has some pitfalls :(

Unknown said...

Pitfalls, I agree. I believe Google maintains their own Closure library that has no warnings (from reading this post). The one that is open sourced is different, they keep pushing stuff from internal to external is my bet, that is why we see some missing stuff and warnings.

Jethro Larson said...

It'd be nice if there was just a simple comment or something for declaring externals.
"globals: jQuery, util"

It feels really awkward to define the function again.