micmath blog
RecentLimiting Variable Visibility in JavaScript
My momma always told me to keep the visibility of my variables as small as possible. Well, okay I can confidently say those words never passed her lips, but I can easily imagine some stern-faced Code Matron wagging a finger and chiding, "Global variables make baby Jesus cry." I won't go into all the whys of limiting the visibility of your variables, the point of this post is to compare a few different hows.
We have all been told that if we want to limit the visibility of a variable in JavaScript there is only one way to do it, and that is to use var inside a function. For example, if you wanted to ensure that a variable x did not touch the larger scope of your program, you could do something like this:
(function () {
var x; // reference to variable x exists in here
alert(x); // undefined
})();
alert(x); // throws ReferenceError: x does not exist here
It is worth mentioning at this point that JavaScript has a very relavant little feature known as "variable hoisting," meaning that variables are effectively always declared at the top of the function, regardless of where you write the var x bit. The example below is effectively identical to example above:
(function () {
// reference to variable x exists in here
alert(x); // undefined
var x;
})();
alert(x); // throws ReferenceError: x does not exist here
So you can't limit the visibility of a variable lexically, by writing the var stuff near the end of a function. If you declare a variable anywhere in a function the effect is the same as declaring it at the top of that function. For that reason it is arguably good practice to actually declare variables at the top of functions--you may as well make it look the same as the way it works, right?
But using var isn't the only way to accomplish our goal. You can also use function parameters.
(function (x) { // reference to parameter x exists
alert(x); // x is undefined
})();
alert(x); // throws ReferenceError
This time the x listed as a parameter of the function actually pops into existence right before the rest of the function body. So it's possible a var somewhere (anywhere) inside the function could overwrite the parameter x everywhere.
And that would appear to be the end of our options, but in fact there is yet another way to achieve this same goal, and it doesn't use functions at all. This time we'll use with:
// assume undefined === void(0);
with( {x: undefined} ) { // reference to property x exists
alert(x); // x is undefined
}
alert(x); // throws ReferenceError
People don't use with very much (for reasons I'll discuss a bit later) but it is designed to do exactly what we are after here: alter the scope chain for just a little while.
But let's compare these two techniques in a more real world situation: here I want to fill an array with the first few integers squared; I need to create a temporary variable named i but I don't want that i to exist or have any effect whatsoever outside my limited 2 or 3 lines of code.
First the functional way:
squares = [];
(function() {
var i = 5;
do { squares[i] = i * i } while (i--);
})();
alert( squares.join(', ') ); // 0, 1, 4, 9, 16, 25
alert(i); // ReferenceError
or alternatively:
squares = [];
(function(i) {
do { squares[i] = i * i } while (i--);
})(5);
alert( squares.join(', ') ); // 0, 1, 4, 9, 16, 25
alert(i); // ReferenceError
And the same using with:
squares = [];
with( {i: 5} ) {
do { squares[i] = i * i } while (i--);
}
alert( squares.join(', ') ); // 0, 1, 4, 9, 16, 25
alert(i); // ReferenceError
You could argue that the functional techniques look a little more sugary than the with technique and thus with is the cleaner way to go, but there are several other points against using with. First is a slightly circular observation that people are likely to be unfamiliar with with and so don't often use it. I suppose we could start a campaign to get more people to use it and therefore solve that problem, but here's why I wouldn't: with, like its ginger-haired stepbrother eval, is an easy to abuse little sucker, and you don't have to use it very much before you start to consider it confusing at least. What's more, it's slower than the functional equivalents shown here. Well, mostly. In some browsers it's actually slightly faster, but this is more than offset by the fact that it is incredibly humongously slower in almost all other browsers.
Of course, in the big picture, we're still only talking small fractions of seconds difference here so if you really just love the syntax of with shown above, you could still get away with using it. I leave that decision to you.
