Javascript referencs and closures
When assigning variables in JavaScript, you really need to know about closures. Which always makes my head hurt. Here are some examples. The examples all create functions I then attach to the onClick handler of an A element.
Code for this section
document.getElementById("a1").onclick = function() { alert(outer); };
inner = outer;
document.getElementById("a2").onclick = function() { alert(inner); };
outer++;
document.getElementById("a3").onclick = function() { alert(outer); };
We start with a variable outer = 10;
1. Attach onlick=alert(outer) here. You would expect: 10
Here we set inner = outer
2. Attach onlick=alert(inner) here. Expected: 10
Now increment: outer++
3. Attach onlick=alert(outer) here. Expected: 11
Examples 1 and 3 both give you 11, while example 2 shows 10. This happens because these functions both have a reference to outer, and outer has the value 11. Example 2 shows 10, because the value of outer was copied into it.
Ok, that was pretty basic. Now we want to attach an onClick handler to a bunch of links. In practice people always want to attach popup tooltips to a bunch of items, but the idea (and problem) is the same.
Code for this section
// create some items
items = new Array();
for(i=0; i<4; i++) {
items = new Object();
items.key = i;
}
// set handlers (this doesn't work because you set a reference)
for(i=0; i<items.length; i++) {
item = items;
document.getElementById("l"+i).onclick = function() {alert(item.key);};
}
// set handlers with function objects. works, but creates an object for every link.
for(i=0; i<items.length; i++) {
item = items;
document.getElementById("lv"+i).onclick = Function("alert("+item.key+")");
}
// set handlers, and store the key in the element. this leaks memory in IE.
for(i=0; i<items.length; i++) {
item = items;
document.getElementById("lk"+i).onclick = function() {alert(this.key);};
document.getElementById("lk"+i).key = item.key;
}
Now we loop over the itmes and set onclick = function(){alert(item.key);}, but that doesn't work because you get a closure that has a reference
to item.
- check value (expected 0)
- check value (expected 1)
- check value (expected 2)
- check value (expected 3)
Now we do the same thing, but use onclick = Function("alert("+item.key+")"), That will be a distinct function object for every link, and will work.
- check value (expected 0)
- check value (expected 1)
- check value (expected 2)
- check value (expected 3)
The other solution: href.onclick = function() {alert(this.key);}; href.key = item.key;.
This also works, but leaks memory on IE because of a circular reference between the DOM Node and the handler function which it can't garbage collect.
Also, I think it is ugly to store values in elements this way.
- check value (expected 0)
- check value (expected 1)
- check value (expected 2)
- check value (expected 3)