classical geek

More Closure Strangeness

My ongoing love-hate relationship with JavaScript closures took another twist the other day, when I discovered something new about their usage.

Let me back up a bit first. Closures are a powerful feature of JavasCript, and they allow you to get things done in a very fluent fashion. I use them quite a bit in my own code these days. They’re also an accident waiting to happen, because there’s no special syntax for creating them, they just happen. They can lead to memory leaks, and there’s no alternative but to use them in order to do several very useful things with JavaScript event handlers and Ajax calls. Knowing how to use them properly requires more understanding of the internals of the Javascript interpreter than a casual web developer ought to need.

OK, onto the demo. Here’s the first bit of code:

<html>
<head>
<style type='text/css'>
.btn{
width:100px; height:100px;
background-color:#fed;border:solid navy 1px;
}
</style>
<script type='text/javascript'>
window.onload=function(){
for (var i=0;i<3;i++){
document.getElementById("clickMe"+(i+1))
.onclick=function(){
alert("clicked! i = "+i);
}
}
}
</script>
</head>
<body>
<div class='btn' id='clickMe1'></div>
<div class='btn' id='clickMe2'></div>
<div class='btn' id='clickMe3'></div>
</body>
</html>

We’re doiong the good thing here, and attaching some event handlers programmatically to DIVs in the document body. The important thing to note for this discussion, though, is the loopp counter variable i, which is passed via a closure into the anonymous event handler function. Later, when the function is invoked - when you click on one of the DIV elements - the event handler will magically remember the value of i.

As it happens, it will remember it wrongly. The reference passed to the closure gets updated with each turn of the loop, so clicking on any DIV will alert a message showing the value of i as 3!! Closures bind references, not values, you see.

How can we get around this problem? Well, there is a way. If we create the closure in a separate function from the loop, then we’ll be running in a different execution context (often referreed to as a frame - but don’t confuse with HTML Iframes!), and a different reference to i will be preserved within the closure each time.

Here’s the correct answer, then.

<html>
<head>
<style type='text/css'>
.btn{
width:100px; height:100px;
background-color:#fed;border:solid navy 1px;
}
</style>
<script type='text/javascript'>
window.onload=function(){
for (var i=0;i<3;i++){
setClickHandler(i);
}
}
function setClickHandler(i){
document.getElementById("clickMe"+(i+1))
.onclick=function(){
alert("clicked! i = "+i);
}
}

</script>
</head>
<body>
<div class='btn' id='clickMe1'></div>
<div class='btn' id='clickMe2'></div>
<div class='btn' id='clickMe3'></div>
</body>
</html>

And even better, it works! But to get it working, we’ve had to visit the concepts of execution contexts as well as closures, in order to hook an integer value up to an event handler. It might not be rocket science, but it’s not far off. There ought to be an easier way to add event handlers to a large number of elements (OK, three elements in our little example), but, for now, I present this curiosity. Hope it comes in useful.

3 Comments

  1. i was asking about this same thing in some forum a few weeks back, and couldn’t find a satisfactory answer for fixing the same problem you mention here without resorting to setting a property in a dom node. seemed like there should be some way to do it without resorting to that. someone suggested using timeout(). ack. stumbled in here looking for the errata for your book, and found the answer to my question. thanks! ‘closures bind references, not values’ was what i didn’t get. you’d think they’d build in unary operator so you could force closures to evaluate a reference.

    Comment by will — May 1, 2006 @ 4:35 am

  2. I think your source of confusion is that you think variables are “passed” to closures. They aren’t. A function acting as a clousure is “bound” to those variables. Any (and I mean *any*) updates to that variable will be seen by the closure. In your solution you “pass” the variable to a different function which will create an entirely new variable which is a copy of the one you passed. This is why it behaves like you want because each time you return the anonymous fuction it’s bound to a different variable.

    This is the same with any language that supports closures. It’s just how they work.

    Comment by Michael Peters — March 23, 2007 @ 1:31 pm

  3. Hi Michael,

    Yes, the closure has a reference to the variable, not to the variable’s value. Passing it as an arg into a function does create a new variable.

    The point that I was trying to make was that it’s necessary to understand these intricacies in order to use closures, which might not be a problem for the likes of you or I, but can seem seriously weird to a newbie/casual JS programmer coming from a language such as Java.

    A year on, my gut feeling about closures has probably shifted more towards the love than hate, as my own programming style has continued to evolve, but I still maintain that they’re the biggest source of confusion in learning JS (and conversely, of ‘getting’ JS as a language once you’ve understood them).

    Thanks for dropping by.

    Dave

    Comment by dave — March 23, 2007 @ 2:51 pm

RSS feed for comments on this post. TrackBack URI

Sorry, the comment form is closed at this time.

Powered by WordPress, Supported by SaveOnRefinance.com