Sunday, December 9, 2007

JavaScript Closures

Closures are a fairly advanced JavaScript feature which I have only seen discussed in special articles rather than tutorials. (Before anyone asks, I don't know how they got that name.) While I wish someone would write a tutorial that would introduce beginners to the concept, I don't have the time and might not have the expertise, so this is going to follow that trend.


First, let's have a definition. A closure is the scope of an outer function made accessible to all inner functions. When I look at code which uses closures, it seems like common sense that outer variables would be available from inner functions, but, at the same time, I wouldn't expect the effect sometimes. The effect can manifest similarly to the private declaration of properties in PHP or Java, or the static declaration of PHP as used at the first of a PHP function. Like both of these, a closure allows variables to be accessible only from within a certain context, across repeated calls to a certain set of functions.


JavaScript With(out) Closures
If JavaScript is written strictly without using closures, it can get pretty cluttered. Let's say I want to return Function B from Function A, then call Function B to manipulate variables initialized in Function A. (What a mouthful.)
<html>
    <head>
        <title>Titled Document</title>
        <script type="text/javascript">
            function funcA(){
                window.varA = 'a variable';
                    function funcB(){
                    alert(varA);
                }
                return funcB;
            }
            window.onload = function (){
                var funcB = funcA();
                alert(varA + '... Uh-oh...');
                    funcB();
            }
        </script>
    </head>
</html>
(The mention of window is unnecessary, but I want to make it obvious that varA is global.) This works, but, as the call to alert in window.onload shows, it also exposes your variables to overwriting (and hacking, but a hacker can always overwrite your functions anyway). This is a more private way to do it (still without closures):
<html>
    <head>
        <title>Titled Document</title>
        <script type="text/javascript">
            function funcA(){
                var result = {
                    varA: 'a variable';
                funcB: function (){
                    alert(this.varA);
                }
                return result;
            }
            window.onload = function (){
                var objA = funcA();
//                alert(varA + '... Uh-oh...');
                objA.funcB();
            }
        </script>
    </head>
</html>
But that's not exactly what we wanted. Because I was restricting myself from using closures, I had to use some other mechanism to do the same thing. I used a custom object, whose member 'funcB' is the focal point of the script. Here's a script that accomplishes the same thing as the previous two, but privately and without the object:
<html>
    <head>
        <title>Titled Document</title>
        <script type="text/javascript">
            function funcA(){
                var varA = 'a variable';
                function funcB(){
                    alert(varA);
                }
                return funcB;
            }
            window.onload = function (){
                var funcB = funcA();
//                alert(varA + '... Uh-oh...');
                funcB();
            }
        </script>
    </head>
</html>
Uncommenting the call to alert in window.onload causes an error, because varA is only defined within funcA's scope (= funcB's closure). Closures allow you to set up code boundaries at your convenience.


Final Notes
Pay attention if you declare several inner functions in one outer, particularly in a loop; the closure is a variable scope (as it exists when the inner function in question is executed) rather than a permanent snapshot of that scope from when the function was declared, and so there is one per call to the outer function, not one per inner function per call to the outer function. So this doesn't work as it would if closures were snapshots specific to inner functions:
function loopyLooper(){
    var demos = [];
    for(var i0 = 0; i0 < 10; i0++){
        demos[i0] = function(){
            alert(i0);
        };
    }
    return demos;
}
var demos = loopyLooper();
for(var demo in demos){
    demo();
}
Here's a correct solution to accomplish this in similar steps (but without using the closure):
function looper(){
    var demos = [];
    for(var i0 = 0; i0 < 10; i0++){
        demos[i0] = {
            func: function(){
                alert(this.num);
            },
            num: i0
        };
    }
    return demos;
};
var demos = looper();
for(var demo in demos){
    demo.func();
}

Here's a loop-less function that expects a snapshot instead of the actual scope:
function loopyLoopless(){
    var i = 0;
    function result(){
        alert(i);//should alert 0
    }
    i = 1;
    return result;
}



//user code

loopyLoopless()();//alerts 1 - The JavaScript interpreter must be broken!
And here's a correct version that preserves the user's control over when and in what context the resulting function is executed (while still using a closure):
function loopless(callback){
    var i = 0;
    callback(function(){
            alert(i);//should alert 0
        });
    i = 1;
}
//user code
loopless(function(result){
        result();//alerts 0
    });

As usual, discussion is invited.

No comments: