Leo's Technical Blog

Javascript, Closures and Parameter-passing Styles

Introduction

user

Leo Soto


javascript

Javascript, Closures and Parameter-passing Styles

Posted by Leo Soto on .
Featured

javascript

Javascript, Closures and Parameter-passing Styles

Posted by Leo Soto on .

Yesterday, a friend asked for help with this Javascript code, which didn't work:

  
    for (var i = 0;i < menu.childNodes.length; i++) {
        if (menu.childNodes[i].nodeName  "SPAN"){
            var element = menu.childNodes[i];
            connect(element, "onclick", 
                    function(){showItem(element)});
        }
    }

The observed behaviour was that clicking on every sub-menu item always showed the latest sub-menu, instead of their own sub-menu (connect is a MochiKit utility function, that among other things, acts as a cross-browser addEventListener). The bug is subtle. I will explain it after showing one possible solution:

  
    var makeShowItemEventListener = function(element) {
        return function(){ showItem(element) };
    }

    for (var i = 0;i < menu.childNodes.length; i++) {
        if (menu.childNodes[i].nodeName  "SPAN"){
            var element = menu.childNodes[i];
            connect(element,"onclick", 
                    makeShowItemEventListener(element);
        }
    }

This works. Why?.

The function makeShowItemEventListener receives a element and returns a function that, when invoked, will show that element (it's a so-called high-order function). So, what's the difference with the previous code, which coded the function directly?

It's all about how the element variable is "passed". On the first code, the anonymous function is a closure, which captures the variable element from its scope. But it captures a reference to the variable, so when the variable changes, the referenced variable inside the closure also sees the change. In other words: they are the same. So all event listeners are bound to the very same element variable which obviously ends pointing to the last sub-menu when the for loop finishes.

The fixed code passes the element variable to another function, and on Javascript, the parameters are passed by value, as on Java, C#, Python, etc.. So, inside the makeShowItemFunction, the formal parameter element is a copy of the original reference, and doesn't change if the actual parameter element is modified.

Finally, considering that MochiKit was already included, we didn't need to make a specialized function which only purpose is to return another function with a fixed parameter. That's what MochiKit's partial() does. So the resulting code is:

  
    for (var i = 0;i < menu.childNodes.length; i++) {
        if (menu.childNodes[i].nodeName == "SPAN"){
            var element = menu.childNodes[i];
            connect(element, "onclick", 
                    partial(showItem, element));
        }
    }

This is an example of why it's important to know and understand your tools, not just using it blindly. It will pay on the long run, not only when debugging problems like the one shown here, but also when developing way better solutions, because you know a better tool for each job.