A common problem with the powerful javascript closures

I recently wrote about closures and how easier your javascript will be to maintain and how good it will look. Now is the time for me to be a Closure-Grinch.

Closures keep a reference to a variable, not a copy

[source:javascript]
// Create a Buy Viagra function
function buyViagra() {
var pills = 2;

// Create a closure to alert the number of pills
function alertPills() {
alert(”Number of pills : ” + pills);
}

alertPills();

// Increment the number of pills
pills += 1;

alertPills();
}

buyViagra();
[/source]

Looking at this code, it can be pretty obvious that the alert containing the number of pills will be “2″ the first time and “3″ the second time. That’s because the closures contains the reference to the variable and not a copy.

The common problem with closures and loop

That’s a more common problem. We use closures in a loop (for or while) and it always keep the last value of the increment.

[source:javascript]
// Functions that will alert a number
var alertFunctions = new Array();

for (var iNumber = 0; iNumber < 3; iNumber++) {
function alertFunction() {
// We use the closures to have access to the variable “iNumber” (from the loop)
alert(”Number is ” + iNumber);
}

alertFunctions.push(alertFunction);
}

// We loop and call each functions
for (var i = 0; i < alertFunctions.length; i++) {
alertFunctions[i](); // Call the function
}
[/source]

We would expect that three alerts pop having 0, 1, 2. But instead, it alert 3, 3, 3. This is because the iNumber variable was 3 when it got out of the loop.

How to solve the problem

[source:javascript]
// Functions that will alert a number
var alertFunctions = new Array();

for (var iNumber = 0; iNumber < 3; iNumber++) {
// Magic here!
var alertFunction = function(x) {
return function() { alert(x); };
}(iNumber);

alertFunctions.push(alertFunction);
}

// We loop and call each functions
for (var i = 0; i < alertFunctions.length; i++) {
alertFunctions[i](); // Call the function
}
[/source]

Step #1
var alertFunction = function(x) {

I create a new function that will take a parameter. The parameter will contain a copy of the variable I send to it.

Step #2
return function() { alert(x); };

I return a function that will alert the copied parameter of step #1.

Step #3
}(iNumber);

I call the newly created function (that returns a function).

Comments

  1. [...] will be to maintain and how good it will look. Now is the time for me to be a Closure-Grinch.read more | digg [...]

  2. jsn April 12, 2007 at 16:07:08

    no need to define alertFunction() multiple times.


    function alertFunction(x) {
    return function() { alert(x) }
    }

    for (var iNumber = 0; iNumber

  3. jimbojw April 12, 2007 at 16:16:57

    If you have access to the Prototype library, the bindAsEventListener can method can help with this.

    http://wiki.script.aculo.us/scriptaculous/show/Function.prototype.bindAsEventListener

    alertFunctions.push(
    (function() {
    alert(this);
    }).bindAsEventListener(iNumber)
    );

  4. BK April 16, 2007 at 09:43:58

    Again, the use of the Function constructor also does the trick here:
    alertFunction=new Function(”alert(’Number is ” + iNumber + “);”);
    alertFunctions.push(alertFunction);

  5. Aaron Faanes April 16, 2007 at 16:48:22

    BK: While that would work, new Function is just a synonym for eval(), and uses of eval() are problematic at best since “complicated ’string ” + concate + “‘ nation is a bitch to ” + write + (and * debug).

    For that example, it’s probably not that important either way, since there’s little security, performance, or clarity issues at all with doing it any way, but I figured it’s worth mentioning my dislike for it in general.

    jimbojw: That ends up using a closure itself anyway, hidden behind a method call.

    Function.prototype.bindAsEventListener = function(/* some args */) {
    var that = this;
    var args = arguments;
    return function() { return that.call.apply(that, args); }
    }

    I’m not saying it won’t work, I’m just saying that Prototype isn’t beyond closures. :P

  6. Dan (maintainer of Javascript Kata) April 16, 2007 at 17:44:51

    @jsn : I didn’t redefine alertFunction, it’s just that I have alertFunction and alertFunctions (with a “s”).

    @BK : Like Aaron said, Function is a bitch. It would be a big problem if the function would be just a bit more complex…

  7. BK April 17, 2007 at 11:50:54

    I agree that they’re Evil to write and maintain but still the only way (I know) of creating a function in a function without closure and for an application that would need to run in a very limited memory space, you could have no choice (except maybe to create the function outside of the function)

  8. Dan (maintainer of Javascript Kata) April 17, 2007 at 12:41:51

    BK, closures are everywhere! Even with Function… try that code.

    var i = 10;
    var f = new Function(”alert(i);”);
    f();

    Update : I am wrong! Look at the next comment from BK

  9. BK April 17, 2007 at 12:46:38

    this works if you put it global.
    Now, try this:
    function test(){
    var i = 10;
    var f = new Function(”alert(i);”);
    f();
    }
    test();

    Result: i is undefined in both FF and IE -> No Closure

  10. Dan (maintainer of Javascript Kata) April 17, 2007 at 12:57:31

    Dammit! You’re right. Thanks!

  11. Gilbert June 14, 2007 at 06:46:19

    function test(){
    var i = 10;
    var f = new Function(’alert(’ + i + ‘);’);
    f();
    }
    test();

    This works

Post a comment

Comments are moderated and innapropriate ones won't be approved. Please respect this public space.