Friday 24 August 2012

Purpose of a Javascript Closure

By default, Javascript knows two types of scopes: global and local.
var a = 1;

function b(x) {
    var c = 2;
    return x * c;
}
In the above code, variable a and function b are available from anywhere in the code (i.e., globally). Variable c is only available within the b function scope (i.e. local). Most software developers won't be happy with this lack of scope flexibility, especially in large programs.

Javascript closures help solving that issue by tying a function with a context:
function a(x) {
    return function b(y) {
        return x + y;
    }
}
Here, function a returns a function called b. Since b is defined within a, it automatically has access to whatever is defined in a, that is, x in this example. This is why b can return x + y without declaring x.
var c = a(3);
Variable c is assigned the result of a call to a with parameter 3. That is, an instance of function b where x = 3. In other words, c is now a function equivalent to:
var c = function b(y) {
    return 3 + y;
}
Function b remembers that x = 3 in its context. Therefore:
var d = c(4);
will assign the value 3 + 4 to d, that is 7.

REM: If someone modifies the value of x (say x = 22) after the instance of function b has been created, this will be reflected in b too. Hence a later call to c(4) would return 22 + 4, that is 26.

Closures can also be used to limit the scope of variables and methods declared globally:
(function () {
 var f = "Some message";
 alert(f);
})();
The above is a closure where the function has no name, no argument and is called immediately. The highlighted code, which declares a global variable f, limits the scopes of f to the closure.

Now, there is a common Javascript caveat where closures can help:
var a = new Array();

for (var i=0; i<2; i++) {
    a[i]= function(x) { return x + i ; }
}
From the above, most would assume that array a would be initialized as following:
a[0] = function (x) { return x + 0 ; }
a[1] = function (x) { return x + 1 ; }
a[2] = function (x) { return x + 2 ; }

In reality, this is how a is initialized, since the last value of i in the context is 2:

a[0] = function (x) { return x + 2 ; }
a[1] = function (x) { return x + 2 ; }
a[2] = function (x) { return x + 2 ; }
The solution is:
var a = new Array();

for (var i=0; i<2; i++) {
    a[i]= function(tmp) {
        return function (x) { return x + tmp ; }
    } (i);
}
The argument/variable tmp holds a local copy of the changing value of i when creating function instances.

No comments:

Post a Comment