11.4 Lexical Scoping and Nested Functions
Functions in JavaScript are
lexically rather than dynamically scoped. This means that they run in
the scope in which they are defined, not the scope from which they
are executed. Prior to JavaScript 1.2, functions could be defined
only in the global
scope, and lexical scoping was not much of an issue: all functions
were executed in the same global scope (with the call object of the
function chained to that global scope).
In JavaScript 1.2 and later, however, functions can be defined
anywhere, and tricky issues of scope arise. For example, consider a
function g defined within a function
f. g is always executed in the
scope of f. Its scope chain includes three
objects: its own call object, the call object of f(
), and the global object. Nested functions are perfectly
understandable when they are invoked in the same lexical scope in
which they are defined. For example, the following code does not do
anything particularly surprising:
var x = "global";
function f( ) {
var x = "local";
function g( ) { alert(x); }
g( );
}
f( ); // Calling this function displays "local"
In JavaScript, however, functions are data just like any other value,
so they can be returned from functions, assigned to object
properties, stored in arrays, and so on. This does not cause anything
particularly surprising either, except when nested functions are
involved. Consider the following code, which includes a function that
returns a nested function. Each time it is called, it returns a
function. The JavaScript code of the returned function is always the
same, but the scope in which it is created differs slightly on each
invocation, because the values of the arguments to the outer function
differ on each invocation. If we save the returned functions in an
array and then invoke each one, we'll see that each returns a
different value. Since each function consists of identical JavaScript
code and each is invoked from exactly the same scope, the only factor
that could be causing the differing return values is the scope in
which the functions were
defined:
// This function returns a function each time it is called
// The scope in which the function is defined differs for each call
function makefunc(x) {
return function( ) { return x; }
}
// Call makefunc( ) several times, and save the results in an array:
var a = [makefunc(0), makefunc(1), makefunc(2)];
// Now call these functions and display their values.
// Although the body of each function is the same, the scope is
// different, and each call returns a different value:
alert(a[0]( )); // Displays 0
alert(a[1]( )); // Displays 1
alert(a[2]( )); // Displays 2
The results of this code may be surprising. Still, they are the
results expected from a strict application of the lexical scoping
rule: a function is executed in the scope in which it was defined.
That scope includes the
state of local
variables and arguments. Even though local variables and function
arguments are transient, their state is frozen and becomes part of
the lexical scope of any functions defined while they are in effect.
In order to make lexical scoping work with nested functions, a
JavaScript implementation must use a
closure,
which can be thought of as a combination of a function definition and
the scope chain that was in effect when the function was defined.
|