Output of the embedded scripts (use show source):
Note: on my browser the following line is "425675NaN331231230907 and 908a"



Javascript scoping concepts

by burt rosenberg

General Theory

Scoping is the process by which a variable name is matched up with a storage location. One way or another, the variable name is introduced, and storage is allocated. The name of the variable then must consistently reference that storage, and not some other. There maybe several variables of the same name, and there must be rules to disambiguate those situations.

Javascript employs dynamic scoping of its variables. This is very different than C or Java which have static scoping.

A model for dynamic scoping is the existence of catalogues of variable names. When a variable is encountered, either by an unadorned statements such as x=3 or adorned with the var keyword such as var x=3, an entry is made in the catalogue for the name x, and a storage location paired to the name. There are multiple catalogue: a global catalogue at the root of the space, and each function introduces its own catalogue for names that are local to the function. The entry to the catalogue only occurs when the statement is actually run. Dynamic scoping is also known as run-time binding for that reason — the name and storage location are said to be bound in the catalogue.

To compare to static scoping, a language like C allocates storage and applies it scoping rules a compile time, long before the program executes. An executing C program has no idea what the programmer named the variables — this information is not needed because the attachment betweeen variables an storage location has been fixed permanently by the compiler. That variables have names is truely a convenience to the programmer, and has no logical significance at all at run time. (But, the map the compiler used to associate variable names and storage locations is often retained as a symbol table, for the use by debuggers that need to "un-compile" bits of code to present reasonably to the programmer which variables are being watched in the debug session.)

Returning the dynamic scoping — when a variable's value is required, the variable name is served in all available catalogues, beginning with the catalogue local for the function in which the name appears, and continues to the catalogue of the function which invoked this function (but see the discussion of closures below) until it looks in the global catalogue, as the catalogue of last resort.

The use of the keyword var places variables in the functions local catalogue, and makes sure the variable is properly hoisted. Without var, a variable defined in a function will place its binding in the global catalogue. Hoisting means that the variable definition acts as if the var statement occurred at the top of the function, or the page in the case of the global catalogue. However, a var statement with variable initialization only hoists the definition, not the initialization. It is as if var x=3 were rewritten as var x; x=3 and the var statement hoisted, and the assignment statement left where it occurs.

At this point, some examples are recommended. These examples will only demonstrate two catalogues: the global and a function local catalogue, associated with the defined function f. We will experiment with variables inside the function f where both f's local and the global catalogue are referenced, and variables outside the function f and only referencing the global catalogue.

A point to stress: please don't write code like this. These are not programming techniques! Many of these codes are aberrations constructed in order to illustrate scoping, and are lousy programming style. Here is practical advice concerning Javascript scoping:

Examples


/*  // example 1 a and b

// x = 1 ; //(a) typical, put x in the global catalogue
function f(){ x = 3 ; document.write(x); } // x must reference values in the global catalogue

//f() ;  //(b) side effect is to place x in the global catalog

document.write(" "+x) ; // generates a ReferenceErrorr for x not defined unless a or b are uncommented to place x in the catalogue.
*/

/*  // example 2 a and b
function f(){ x = 3 ; document.write(x); }

document.write(" "+x) ;

//x = 1 ;  //(a) x not hoisted? This doesn't define X
// var x; //(b) this will hoist x and the error in the document will be the value of x is undefined, and will print "undefined" rather than crashing
*/

/*
// example 3 a, b, c
//function f(){ var x = 3 ; document.write(x); }   //(a) recommended, x is bound in the namespace for f, not global
//function f(){ document.write(x); var x = 3 ; }  //(b) shows hoisting, x is defined but not initialized ("undefined", but not in the sense of "not defined")
function f(){ x=3; document.write(x); var x; } //(c) perverse!, but the var x is as if it were at the top of the function definition

f() ;  // whether commented or uncommented, the x below generates a reference error, but f runs fine

document.write(" "+x) ; // generates a ReferenceErrorr for x not defined, no x in the global catalogue.
*/

Example 1:

This is a pretty typical piece of elementary javascript. The variable x inside the function f will bind in the global catalogue. The binding will take place when f is invoked, not when it is defined. If there is already a name x in the global catalogue when f is invoked, that binding will be used; if not, it will be created.

With (a) and (b) commented, the document.write statement generates an error, because x is being used and there is no entry in the global catalogue for x. The statement (a) if uncommented will define x and there will be no error. Alternatively, the statement (b) if uncommented will also define x, because f will be invoked. If both (a) and (b) are uncommented, x will be defined by statement (a) and that binding will be used in the function f when invoked in statement (b).

Although this is typical novice programmer code, this sort of code should be avoided. The problem is that it might not be evident from the caller of f that the variable x will be modified as a side effect. In fact, in programs where such code is used, a programmer cannot safely call any function without reading carefully the entire body of the function. That's no fun — we want to be able to use a function for its obvious effects and not worry if some variable in the function just happens to have the same name as a variable we are using.

Example 2:

This demonstrates hoisting. On Firefox 15.0 the variable is not hoisted unless var is used. With both (a) and (b) commented, the code generates a reference error. If (b) is uncommented, the var statement is hoisted to the top of the page, and the name x is entered in the global catalogue. The code does not give an error, however the value of x on use is "undefined". I cannot explain why uncommenting (a) does not do the same, but it doesn't. The x is not hoisted in the case of a naked definition of a variable, when in the global context. In function context it means another thing, it means reference the global context, and therefore it doesn't matter if it hoists or not.

Example 3:

This gives three examples of use of the function local catalogue. The var keyword means that the name, if not defined in the local catalogue, should create such a definition. Use of the name in the function will then refer to this local version. Hoisting means that the var introduced variable anywhere in the body of the function will be treated as if it occurs at the beginning of the function. Sub-example (c) is a perversion to prove this point — that naked x will be found in the local catalogue because the var occurring later will be hoisted up.

Sub-example (b) reminds us that hoisting does not move any initializations. The write will find the value of x to be undefined, however the variable will be defined, and no error will occur.

If the invocation of f just after the function definition were removed, all all three sub-examples, an error would occur on the following write. None of the three sub-examples create an entry in the global catalogue.

Closures (function passing)

Javascript allows functions to be passed as arguments to other functions. If the function f is defined, f() invokes the function (understanding that the parenthesis will enclose the proper number of arguments) where as just the name is the function itself.

Example 4 demonstrates the function g defined to operate on one variable, an leave the side effect of advancing i by one. Looking at the definition of i we see that x is local, bound to the local catalogue to the x named as a parameter, and i is not found in the local catalogue, so it will be found in the global catalogue.

Function h introduces the notion of a function as a argument. The argument f is a function, because it is used in the body of h as f(x). The function also enters the variable i into the the local catalogue for h by the statement var i. When h is called, function g is supplied as the value of f. So the statement f(x) inside h is in this invocation g(x). The question is, inside g, what is i bound to?

The answer is, to the global i. Although the binding is not effectuated until g is run, the eventual answer to this question is fixed by the text of the code. The variable i visibly reference the global catalogue at function definition, so this is what will be true in all uses.

Example 5 shows the effect of rewriting function g with var i. Note that each time g2 is run, it returns the same value. The variable i is in the local catalog for g2, and it is set to zero at the start of each run. (Actually, the catalog might be dropped between runs. If there is no need to remember values in a catalog for a later run, it need not be created, and if created, it need not be retained.)

In example 6, the function fo returns as its value a function. The function is anonymous, in that the definition does provide a name, but a name is not needed because we will place the returned value into the variable fi, and fi is now a function that can be invoked by fi(). The function fi is defined when the local catalogue of fo is available, but invoked outside of this environment. However, the creation of this function includes a closure that includes a reference back to the catalogues in effect when the function was defined. In effect, the catalogue of fo, which as a local binding for i, will be used whenever fi is invoked.

You can also see that that dictionary is created when the function is created and lives along side the function. Successive calls to fi increment i inside it's closure. When fo is called a second time, a new closure is created, and it is capture in the variable fj. The i used in fj referes to this closure, and calls to fj increment that version of i.

Although javascript has an object notation, example 7 shows how objects can be created using closures. Typically, and object is instanced, and that object contains instance variables. Each instance of the object has like-named instance variables, but they refer to different storage locations. By returning a function created inside an environment, we trap the enviornment in a closure, freshly created for each invocation of the function. The bindings in this closure are instance variables.

In order to get this to work, the returned value must be a compound data type which contains all of what would otherwise be called the object's methods. When they refer to variables, those variables will be bound by preference in the closure. (If not found, in our example, they will default through to the global catalogue.) Hence the methods work on instance variables, each individual to the instance.


// example 4

function g(x){  i= i + 1 ; return x+i ; }

i = 1 ;
document.write(g(2)) ; //writes 4
document.write(i) ; // writes 2

function h(x,f) { var i=3 ; return f(x) ; }
document.write(h(2,g)) ; // writes 5
document.write(h(2,g)) ; // writes 6
document.write(h(2,g)) ; // writes 7
document.write(i) ; // writes 5

// example 5

function g1(x){ var i = i+1; return x+i; }
document.write(h(2,g1)) ; // NaN value

function g2(x){ var i = 0; i=i+1; return x+i; }
document.write(h(2,g2)) ; // write 3
document.write(h(2,g2)) ; // write 3

// example 6

function fo() { var i=0; return function(){ i=i+1; return i;}} // creates a closure in which lives i
fi = fo() ;
document.write(fi());  // writes 1
document.write(fi());  // writes 2
document.write(fi());  // writes 3
fj = fo() ;
document.write(fj());  // writes 1
document.write(fj());  // writes 2
document.write(fj());  // writes 3

Objects, inner functions

In example 6 we created a closure by passing an inner function as the return value of the outer function. When invoked for the first time, the variables are bound in the closure, and the closure persists attached to the function. In this way, something akin to an object with instance variables was created.

Although the code in example 6 is perfectly fine, Javascript provides for objects whose syntax is a bit more sensible. Rather than have the instance variables being bindings in a function's closure, they are stored as an object's properties. Example 7 continues to demonstrate this style. The scoping of properties is entirely different than that of variables. Rather than catalogues, which are maintained behind the scenes, properties are referenced through the object, which must be presented. The magical this variable is set to make this work out.

Finally, example 8 presents an inner named function. The function keyword acts as the var keyword. The function name is entered into the containing function's local catalog, and definition is hoisted to the top of the function definition, so that no matter where f_inner is defined, its scope is the enter block of f_outer. However, outside f_outer, f_inner is bound.


// example 7

// objects with closures (scheme-like)

function new_object() { var i=0; var j=0; return { 
            get_i : function (){ return i; },
            get_j : function (){ return j; },
            set_i : function (k){ i=k ; },
            set_j : function (k){ j=k; }}
            }
            
o1 = new_object() ;
document.write(o1.get_i()) ;  // writes 0
o1.set_i(9) ;
document.write(o1.get_i()) ;  // writes 9
o2 = new_object() ;
document.write(o2.get_i()) ;  // writes 0
o2.set_i(7) ;
document.write(o2.get_i()+" and "+o1.get_i()) ; // writes 7 and 9

o3 = { i: 0, j: 0, 
            get_i : function (){ return this.i;},
            get_j : function (){ return this.j;},
            set_i : function (k){ this.i=k;},
            set_j : function (k){ this.j=k;}
    }
document.write(o3.get_i()) ;  // writes 0
o3.set_i(8) ;
document.write(o3.get_i()) ;  // writes 8   
 
// example 8

function f_outer(){ var i = 'a' ; 
    document.write(f_inner());
    function f_inner() { return i ; }
    }

f_outer() ; // writes a
f_inner() ; // Reference error, f_inner is not known in global catalogue



Copyright 2012 burton rosenberg
Last modified: 2 sep 2012