Monday, December 19, 2005

Dynamic scope

Now I'm confused by another aspect of JavaScript scope:
var f = function() { alert("default") }
function test(b) {
if (b) {
function f() { alert("shadowed") }
}
f()
}
Evaluate test(true), and you'll see "shadowed". Evaluate test(false), and you'll see "default". Apparently function declarations are another instance where JavaScript is not lexically scoped (in addition to this, arguments, and the behavior of references to unbound variables).

Update: Oh, I forgot to mention the with construct -- another instance of dynamic scope.

Update: Also note that this is not the same thing as the behavior of var. If we write this function instead:
var f = function() { alert("default") };
function test(b) {
if (b) {
var f = function() { alert("overridden") };
}
f()
}
Then with test(true) we still get "overridden", but with test(false) we get an error from trying to apply undefined as a function. In other words, in this example, we have normal lexical scope with hoisting.

Update: I think this settles it--it's pretty much what it looks like. In chapter 13 of ECMA-262, the dynamic semantics of a FunctionDeclaration is to create a new binding in the "variable object" (ECMA-262 terminology for an environment frame) upon evaluation of the declaration.

If you imagine an environment as a list of frames, then in a lexically scoped language, this is a statically computable list. In a language with dynamic scope, frames in the environment may be generated or mutated at runtime, so it's not statically computable. JavaScript has some of both, so there are some things you can know statically about a variable reference, and some things you can't.

Update: The saga continues... I realized that I couldn't actually derive the above example from the official ECMAScript grammar! I see that others have made this surprising discovery as well: you can't nest FunctionDeclarations beneath Statements. Furthermore, you can't begin an ExpressionStatement with a FunctionExpression. As a result, it's impossible to derive the above program.

However, in practice, JavaScript engines do appear to accept the program, and use the dynamic semantics from chapter 13 as I describe above. But for strict ECMAScript, I can't seem to produce an example. I thought I could still force the issue by placing the nested function at the top-level of the enclosing function body and conditionally returning. But I was thwarted by two more interesting phenomena. First of all, when a var declaration and a function declaration compete to bind the same identifier, the var always wins, apparently. So this always produces "default", regardless of the value of b:
function test(b) {
var f = function() { alert("default") };
if (!b) {
f();
return;
}
function f() { alert("overridden") }
f()
}
Second, nested function declarations appear to be hoisted to the top of the function body. So the following example always produces "overridden":
var f = function() { alert("default") };
function test3(b) {
if (!b) {
f();
return;
}
function f() { alert("overridden") }
f()
}
Curious. At any rate, the behavior of popular JavaScript engines (Firefox and Rhino) seem to be a generalization of ECMAScript (by allowing FunctionDeclarations to appear nested within Statements) that is consistent with the spec (by obeying the behavior specified by section 13).

Now I need to confirm these two more properties of the language with the spec.

2 comments:

Sjoerd Visscher said...

Function declarations are always hoisted to the top. Variable declaration are hoisted to the top as well, but below function declarations. Variable assignments stay where they are.

The spec is quite specific about that. (page 37/38). F.e.: "If there is already a property of the variable object with the name of a declared variable, the value of the
property and its attributes are not changed. Semantically, this step must follow the creation of the
FormalParameterList and FunctionDeclaration properties. In particular, if a declared variable has the same name as a declared function or formal parameter, the variable declaration does not disturb the existing property."

Dave Herman said...

Why thank you! That was so much easier than combing the spec myself. :) Much obliged.