I have been using JavaScript for more than 15 years with some projects running to several thousand lines. But just discovered that for all these years I have misunderstood the scope rules for variables. I had assumed they were block scoped, but in fact every variable is effectively declared at the beginning of the function.
So if you write:
function f() { for( var i=0; i<10; i++ ){ var i_squared = i * i; // more stuff ... } }
This is treated as if you had written:
function f() { var i, i_squared for( i=0; i<10; i++ ){ i_squared = i * i; // more stuff ... } }
The Mozilla Developer Network describes the basic principle in detail, however, does not include any examples with inner blocks like this.
So, there is effectively a single variable that gets reused every time round the loop. Given you do the iterations one after another this is perfectly fine … until you need a closure.
I had a simple for loop:
function f(items) for( var ix in items ){ var item = items[ix]; var value = get_value(item) do_something(item,value); } }
This all worked well until I needed to get the value asynchronously (AJAX call) and so turned get_value into an asynchronous function:
get_value_async(item,callback)
which fetches the value and then calls callback(value) when it is ready.
The loop was then changed to
function f(items) for( var ix in items ){ var item = items[ix]; get_value_async( item, function(value) { do_something(item,value); }; ); } }
I had assumed that ‘item’ in each callback closure would be bound to the value for the particular iteration of the loop, but in fact the effective code is:
function f(items) var ix, item; for( ix in items ){ item = items[ix]; get_value_async( item, function(value) { do_something(item,value); }; ); } }
So all the callbacks point to the same ‘item’, which ends up as the one from the last iteration. In this case the code is updating an onscreen menu, so only the last item got updated!
JavaScript 1.7 and ECMAScript 6 have a new ‘let’ keyword, which has precisely the semantics that I have always thought ‘var’ had, but does not seem to widely available yet in browsers.
As a workaround I have used the slightly hacky looking:
function f(items) for( var ix in items ){ (function() { var item = items[ix]; get_value_async( item, function(value) { do_something(item,value); }; ); })(); } }
The anonymous function immediately inside the for loop is simply there to create scope for the item variable, and effectively means there is a fresh variable to be bound to the innermost function.
It works, but you do need to be confident with anonymous functions!
Hi Alan,
Using Node.js the closure approach you’ve used is pretty idiomatic. It seems to be the natural way of expressing async stuff. I still really think of closures as ‘objects’ 😉
Graham