Yet another article about closures

June 7th, 2009 Comments Off on Yet another article about closures

…or when variables get too friendly and cause trouble

JavaScript is a functional language. Because of its functional nature you can do really fun things that may not be readily apparent. One of my favorite things about JavaScript is its implementation of closures, a function complete with its own private scope. Why would that ever be useful? It helps get around one of my least favorite things about JavaScript: scope. A closure is a function that, while it has access to globally-scoped items, has its own, bound local scope. You know those times when you only want a variable to apply to a certain event? No? How about an example?

I bet at first glance you’ll be able to tell me what this bit of code is supposed to do, but not why it doesn’t work as expected.

function addLinks()
{
    for (var i=0, link; i<5; i++)
    {
        link = document.createElement("a");
        link.innerHTML = "Link " + i;
        link.onclick = function ()
        {
            alert(i);
        };
        document.body.appendChild(link);
    }
}
window.onload = addLinks;

To be clear, this is a small function that will add five links to the page that read Link 0, Link 1, Link 2, etc. and, when clicked, will pop up an alert box with that link’s number, such as 0, 1, 2, etc. I know appending to the DOM inside a loop is bad practice and that these links are not inside of a block-level entity. For now pretend that these things aren’t important. What is important is that clicking on any of these links will always alert 4 instead of the number belonging to that link.

Global scope is the enemy of predictability

As the loop iterates, the variable i is being incremented and that number is applied to the individual links as they are made – so what’s happening here? JavaScript’s ultra-friendly, as-global-as-possible scope is stealthily making trouble. For example, if you were to break that loop when i was set to 3, for example, all links would alert 3.

You see it yet? Exactly. The variable i is set in the global sense of the page and each link is referring to the global instance of i, not its own local scope. So if I were to run this function then type javascript:i='foo'; in my address bar each link would alert foo. You’re right to think there has to be a way around this.

Bringing them together

So you have a function using a variable but the scope is out of whack? Let’s see if a closure can help! The trick will be to create a function that only knows what you tell it and make that function what gets called on the onclick. If that doesn’t make sense, read it again and then look at the code. The syntax and execution of closures is fairly counter to most programming techniques so putting yourself in that mindset will take some adjustment. A bit of updated code should help.

function addLinks()
{
    for (var i=0, link; i<5; i++)
    {
        link = document.createElement("a");
        link.innerHTML = "Link " + i;
        link.onclick = function (num)
        {
            return function ()
            {
                alert(num);
            };
        }(i);
        document.body.appendChild(link);
    }
}
window.onload = addLinks;

The onclick is now bestowed with a closure and that closure is binding the i‘s value into its own local scope so that you can change i to be whatever you like and the closure will always remember what it was when you called it. The closure is invoked by the (i) at the end of the function, making it a self-invoked function. When it is invoked the i is passed in, assigned to the num variable which makes it a local, contained variable. Then an anonymous function is returned that will be called when the onclick is triggered. That anonymous function doesn’t have its own num variable so it looks up the scope chain, sees one in the onclick and uses it.

That last paragraph might need a few passes to fully understand. Don’t worry, closures are a complicated concept to learn for programmers not well versed in functional languages. In upcoming entries I’ll talk about stretching JavaScript in two different but powerful ways: lambda functions and exploring public and private variables. Stay tuned!

Recommend reading:

Comments are closed.