Thursday, February 23, 2012

JavaScript variable scope and closures

I wrote a bug recently. I didn't mean to, it just happened. I thought the code looked good and two other code reviewers did too. Of course none of us are JavaScript programmers and clearly none of us understood closures and variable scope properly. This is one of the basic things about JavaScript that seems to catch people out all the time. THERE IS NO BLOCK SCOPE - ONLY FUNCTION SCOPE! I blame the C-like syntax for giving people a false sense of security. Take the following contrived example. Three buttons called One, Two and Three. Add a click event handler to each that simply displays the name of the element. The following code DOES NOT WORK. Some people (me included, not so long ago) would have expected that clicking One would show '#one', Two would show '#two' and Three would show '#three'. But they all show '#three'. Why? Because scope is function-based in JavaScript. And 'url' is declared in the document.ready function. Clicking on any of the buttons will result in the last value assigned to the url (i.e. '#three') to be displayed.

$(document).ready(function() {
    var links = ['#one', '#two', '#three'];
    for(var i=0; i<links.length; i++) {
       // oops! The scope of url is the function - not the for loop block
       var url = links[i]; 
       $(url).click(function(e) {
           alert(url); 
       }); 
    }
});

To fix this, we introduce a closure to limit the scope of that variable

$(document).ready(function() {
    var links = ['#one', '#two', '#three'];
    for(var i=0; i<links.length; i++) {
       displayId(links[i]);
    }
});

var displayId = function(url) {
    $(url).click(function(e) {
        alert(url); // now the scope of url is within this function
    });
}
So lessons learned.
  • Have a good grasp of the fundamentals of a language before ploughing ahead coding in it
  • Only do code reviews with people who UNDERSTAND THE LANGUAGE!
  • Normal advice of declaring variables as late as possible can be BAD ADVICE in JavaScript - declare at the top of a function instead.

1 comment: