Tuesday, September 19, 2006

Block Scoping in JavaScript

Contrary to C, JavaScript does not directly support block-level scoping, only at the objects and function levels. (Global scoping is really just scoping within the window object.) Thus, the second outer block alert will be 3, not 2:

function foo0() {
var bar = 2;
alert("outer block: "+bar);
if (true) {
var bar = 3;
alert("inner block: "+bar);
}
alert("outer block: "+bar);
}


Because JavaScript scoping is at the function and object levels, block scoping can be simulated with anonymous functions and anonymous objects.

Function-based Block Scoping



Anonymous functions can simulate block scoping by defining a closure and calling it:

function foo1a() {
var bar = 2;
alert("outer block: "+bar);
if (true) (function() {
var bar = 3;
alert("inner block: "+bar);
})();
alert("outer block: "+bar);
}


The parentheses around the closure are necessary to avoid a syntax error when calling the closure immediately. Formal arguments are also considered to be at the function scope, and they can be used to bind the block scoped variables:

function foo1b() {
var bar = 2;
alert("outer block: "+bar);
if (true) (function(bar) {
alert("inner block: "+bar);
})(3);
alert("outer block: "+bar);
}


Another implementation of block scoping with closures uses the new operator that automatically calls its function:

function foo1c() {
var bar = 2;
alert("outer block: "+bar);
if (true) new function() {
var bar = 3;
alert("inner block: "+bar);
};
alert("outer block: "+bar);
}


The syntax of the new operator is a bit tidier.

Object-Based Block Scoping



Just as an anonymous function can implement block scoping, so too can an anonymous object, using the with statement:

function foo2a() {
var bar = 2;
alert("outer block: "+bar);
if (true) with({ bar:3 }) {
alert("inner block: "+bar);
}
alert("outer block: "+bar);
}


Of all of these examples, the with statement has the tidiest syntax -- but only those members in the anonymous object are block scoped. Thus, variables of both function and block scope can be mixed within the same block:

function foo2b() {
var bar = 2;
var baz = 1;
alert("outer block: "+baz);
if (true) with({ bar:3 }) {
var baz = 4;
alert("inner block: "+baz);
}
alert("outer block: "+baz);
}


In this example, the second outer block alert for baz is 4, because baz still has function scope. It remains to be seen whether this ability to mix scoping is a bug (more confusing) or a feature (more flexible) for the with statement version.

No comments: