Javascript doesn't play exactly like I want it to. Check out this example:
var arr = [];for (var i = 0; i var func = function() { alert(i); }; arr.push( func );}arr[5]();At first glance, this should create an array with 10 elements, each with a function that creates an alert of it's index. However, the code as written alerts '10'. Try it. Why?
The reason is actually that Javascript is dynamically scoped (NO IT'S NOT, CHECK BELOW FOR UPDATE). Instead of deciding which frames to check for variables at compile time (or just based on the source), nothing is set before runtime. That said, Javascript's scoping rules are very simple. First, look in the local scope. If the variable you want isn't there, look at the scope that function was created. Keep going up until you hit the global scope, at which point throw an error. However, since all this resolution happens at runtime, all the variables have whatever value has been recently stored there. In our example:
1) Look in the body of the function for i. This fails.
2) Look in global scope, where we declared arr and i. This succeeds, and the last value to be stored in i was 10 (which caused the loop to break). Therefore, alert 10.
Just for fun, try this one:
var arr = [];for (var i = 0; i var func = function() { alert(i); }; arr.push( func ); if ( i == 5 ) { arr[5](); }}arr[5]();This produces 2 alerts: '5' and '10'! If this doesn't make sense, just apply those scoping rules. At the time of the call of the first function, we're only halfway into the loop, and i does actually equal 5, so it produces 5. Afterward, the same rules apply, and i has increased to 10, so we see '10' as our alert.
The place where this sort of thing occurs a lot is in callback handlers. If you're creating a number of objects, and you want to associate a modified callback handler to each one, you can't do this:
for (var i = 0; i < objects.length; i++) { objects[i].setCallback(function() { objects[i].doSomething(); };}The associations all work properly, but on lookup, objects[i] always resolves to the last object! To solve it, you'll have to do something like
for (var i = 0; i < objects.length; i++) var create_callback = function(local_object) { return function() { local_object.doSomething(); }; }; objects[i].setCallback( create_callback(objects[i]) );}This makes sure that there is always a function with the proper scope associated with it -- here
local_object is always the correct object.
The reason this behavior is a bit confusing is that Javascript is one of the few languages that does dynamic (as opposed to lexical scoping). Emacs Lisp is the only common language I know of that is purely dynamically scoped, and Perl and Common Lisp both let the programmer choose on a per-variable basis. For instance, in Perl, you can say
my $foo to indicate lexically scoped variables, and
local $foo to indicated a dynamically scoped variable.
Anyway, hopefully this helps anybody who's been stumped by this sort of bug before.
UPDATE: Arg, I'm an idiot.
All the examples above are correct, but my reasoning is totally off. Javascript is lexically scoped, but blocks (for loops, switch statements, etc.) don't create new frames. Therefore, if you're looping through an array and creating a bunch of functions, the next frame up is your function with the for loop in it, and, like I said above, it'll find your loop variable with the value it had at the end of the loop. To get it to close over those variables, you need to create a new frame, which can only by done by creating a new function. By creating that function in the loop, get those closed over variables inside each created function, which is probably the behavior you want.