var
statement with variable declaration statements in the languages they come from and use it the same way. And at some casual level that's reasonable; but it can lead you down a misleading path...Consider this code:
function foo()
{
var ar;
// ...do some stuff, create an array in 'ar'...
for (var index = 0; index < ar.length; ++index)
{
doSomethingWith(ar[index]);
}
}
This is a common idiom, but a misleading one. You might think that index
is only defined within the for
loop (that's certainly the impression we're giving in the code). But it's not true: In fact, index
is defined throughout the function -- within the loop, outside the loop, above the loop, and below the loop. The var
statement defines a variable within the current scope (all of it, not just "from here on"), and unlike some other languages, in JavaScript blocks don't have any effect on scope; only functions introduce a new scope.Consequently, the above function can be written as it is above, but also with the
index
declaration......at the top:
function foo()
{
var ar;
var index;
// ...do some stuff, create an array in 'ar'...
for (index = 0; index < ar.length; ++index)
{
doSomethingWith(ar[index]);
}
}
...at the bottom:function foo()
{
var ar;
// ...do some stuff, create an array in 'ar'...
for (index = 0; index < ar.length; ++index)
{
doSomethingWith(ar[index]);
}
var index;
}
...anywhere in the middle:function foo()
{
var ar;
// ...do some stuff, create an array in 'ar'...
for (index = 0; index < ar.length; ++index)
{
var index;
doSomethingWith(ar[index]);
}
}
...or even all of them!function foo()
{
var ar;
var index;
// ...do some stuff, create an array in 'ar'...
for (var index = 0; index < ar.length; ++index)
{
doSomethingWith(ar[index]);
}
var index;
}
We can get away with that last one because a var
statement defining a variable that already exists in the current scope does not replace the variable (this is what keeps you from accidentally masking your function's arguments, or even the arguments
array that's provided for you).This seems like an odd way to define the
var
statement until you get into the plumbing of JavaScript and how it sets up calls to functions. You can get into some of that by reading my earlier post, Closures are not complicated, but the net effect of the plumbing is that all var
statements are treated as though they were at the top of the function (if they have initializers, those become assignments and stay where they are).So does that mean that the common idiom of declaring an indexer within the loop statement is "wrong"? Well, that's a matter of perspective, and
But the further your code gets from expressing what's really happening, the easier it is for someone reading the code later (perhaps you!) to get the wrong end of the stick and introduce a problem. For example, suppose you have a 30-some-odd-line function and the loop appears in within the body of a conditional about two-thirds of the way down:
function foo(someArray)
{
var thingy;
var otherThingy;
// ...20 lines of code...
if (thingy > otherThingy)
{
for (var index = 0; index < someArray.length; ++index)
{
doSomethingWith(someArray[index]);
}
}
// ...10 more lines of code...
}
Six months after you write this, Mike edits the function and needs to remember the index of something at the top so he can do something with it at the bottom; he declares an "index" variable, sets index
at the top, and then uses it at the bottom, having missed the loop:function foo(someArray)
{
var thingy;
var otherThingy;
var index;
index = findSomething(someArray);
// ...20 lines of code...
if (thingy > otherThingy)
{
for (var index = 0; index < someArray.length; ++index)
{
doSomethingWith(someArray[index]);
}
}
// ...10 more lines of code...
restoreSomething(someArray, index);
}
Mike's introduced a bug, an irritating, intermittent bug. Sometimes the restoreSomething
call at the end fails for some reason; not always, mind, but sometimes. (Because index
gets set by the loop, but only when thingy > otherThingy
.)Obviously, this bug could have been avoided if Mike had read through the entire function carefully before making his mods. Or if you'd chosen a different name for your index variable (in hopes of reducing the odds of Mike using it). Or it could have been caught by thorough unit tests that explore all conditions (and then Mike would have to go back and fix it).
But let's throw Mike a bone, eh? If we declare the variable in the text in the same place it's defined by the interpreter at runtime, we help him avoid making the mistake in the first place. And we like Mike, we don't want to trip him up...right?
Regardless of your decision about how to write your code, though, understanding what
var
is really doing can help you get that code doing what you want it to do.
Yet another reason to use each/forEach for enumeration as a way to encapsulate such annoying little things like conflicting index variables. We have closures - why not let them deal with it implicitly : )
ReplyDeleteBest,
kangax
@kangax - True enough for this particular example, yes. But the issue isn't an issue with loop counters, that was just my example. It could be anything -- using 'element' to refer to an HTML element we're going to use, etc. It's yet another reason to keep functions short and tight, but in my view it's also a good reason for declaring all vars at the point they take effect -- the top of the function.
ReplyDeleteGreat article, I agree that in-loop declarations are needlessly confusing in a function scoped language like JavaScript.
ReplyDeleteAlso, a point about var that I don't see mentioned often enough is that it's not optional, because without it variables are global in scope. (reference: MDN)
It may be JavaScript 101, but coming from Python I was very surprised when I learned that the hard way.
Thanks,
Sam