Monday, May 10, 2010

Intuition When Using Closures

I got into a debate with somebody in #javascript today regarding closures: how they work, how they should work, and are they intuitive. This person had run into a problem similar to the one I posted about earlier. In any case, as the discussion was coming to a close, I created some examples which somebody suggested I blog about. Let's start with a simple piece of code that more or less shows the original problem.

var i = 0; 
el.onclick = function() { print(i); }; 
i++;

In the above example what will be printed? This person argued that, intuitively, "0" should be printed. Of course the correct answer is that "1" will be printed. I can understand how this may be confusing to someone new to Javascript. However, take another example:

var i = 0;
var func = function() { i++; };
func();
print(i);

If I were to show this piece of code to someone, they would probably say that it makes sense. Of couse "1" will be printed. It is intuitive (and correct). Following that line of reasoning:

var i = 0;
var func = function() { print(i); };
i++;
func();

This code also seems to make sense, especially in light of the previous example. The only real difference is that this example reads/prints the variable, and the previous one modified the variable. In both cases "1" will be printed.

Now we're back to the original code:

var i = 0; 
el.onclick = function() { print(i); }; 
i++;

Note that the only difference between this piece of code and the previous one is when the function is executed. In one the function is executed right away, in the other the function is executed as a click handler. In both cases i++ occurs first and in both cases "1" is printed. This is consistent behavior and somewhat shows how closures can morph from something which "just makes sense" into something that isn't quite as intuitive.

The reason for this behavior is that closures work by pointing to the original variable by reference, if you will, not by value - even for primitives. So when the anonymous function runs, i is still pointing back to the original i which has been incremented to 1.