jmnf:3 Scope

javascript magic-ninja-foo episode 3 Scope, it's not just a mouthwash.

Since javascript has traditionally been used for quick and dirty code snippets on web pages, I noticed that a lot of the example javascript code out there defines everything globally. While this is fine for quick pieces of code, writing anything much more substantial than form validation or changing mouse-over images requires looking more thoroughly into the language and how javascript works.

While people are often confused by javascript's implementation of objects, a fundamental understanding of the language leads to the realization that everything is an object or a property of an object. When you define variables and functions in the 'global namespace' what you are doing is taking advantage of the fluid nature of objects in javascript (see previous article) to add properties and methods to the global object. In javascript that's embedded in a browser, this global object usually has a self-refernetial property called 'window'. There is an important thing to think about here, variables and properties of objects are essentially the same thing.

How do we avoid defining everything in the global namespace which is equivalent to adding to the global object? Js uses the concept of 'closures'. A closure is essentially the creation of a new scope bound to a particular function. In javascript a closure is defined between the curly braces of a function. In essence a closure gives a function it's own variable namespace. To define a variable in this new namespace you must use the "var" keyword. Leaving out "var", when you intend to create a 'local' variable, always refers to (or creates!) a global variable. This is a common typo that can lead to lots of headaches.

Consider the following code:

var location="outside"; /*global*/
function WhereAmI() {
   var location="inside"; /*local*/
   return location;
}
alert(WhereAmI()); /*returns local*/
alert(location); /*returns global*/

Upon execution this js code should fire two browser alerts, the first reports "inside" (the variable inside the function) and the second reports "outside" (the global variable). The two variables "location" do not collide because the function WhereAmI() has its own namespace. If the "var" had been left off of the above code, the global variable would have been updated and used, and both alerts would have returned "inside".

It is also possible to create nested closures. Defining a function within another function creates a new scope/namespace for that function. This 'inner' function has access to all of the variables in the 'parent' closure as well as the global namespace. Javascript has a notion of a 'scope chain'. When a variable fails to resolve in the local scope, the parent scope is checked. If it fails to resolve there, it continues up the chain until it resolves or it reaches the end of the scope chain which is the global scope.

Lets look at an example of nested javascript closures.

var location="outside/global";
function level(inNum) {
   var location = "in the first level";
   var fl = "firstlevel";
   /*defined second nested closure*/
   function secondlevel() {
      var location = "in the second level but I have access to "+fl;
      var sl = "second level";
      /*defined a third nested closure*/
      function thirdlevel() {
         var location="in the third level, but I can get to "+sl+" and "+fl;
         return location;
      }
      var temp1 = thirdlevel();
      /*if asked for, assign 'location' to the return
        value*/
      if(inNum > 2) {
        location = temp1; 
      }
      return location;
   }
   var temp2 = secondlevel();
   /*if asked for, assign 'location' to the return
      value*/
  if(inNum > 1) {
    location = temp2;
  }
  return location;
}
alert(location);
alert(level(1));
alert(level(2));
alert(level(3));

The output of this code is:

outside/global
in the first level
in the second level but I have access to firstlevel
in the third level, but I can get to second level and firstlevel

This js is color coded, red, green, and blue to denote the different closures/namespaces. Notice in the above code that the fl and sl variables belong to the 'parent scope' but are still accessible in the nested closures. Also note that the argument "inNum" is accessible at all levels. The variable 'location' is local in each level's namespace and does not collide.

We should take a minute to reflect on what's really going on during the creation of these closures/namespaces. Taking into account that everything is an object, what object are the new variables/properties associated with? Essentially what happens is that javascript creates a new 'execution object' for each call to a function. When the function defines local variables, these variables are added to the 'execution object' as private properties. When the function returns, if there are no references to it, the execution object will be garbage collected. Further calls to the function will create new execution objects.

I added the emphasis above because there is a way to manipulate this object/namespace creation process to create functions with state. In javascript, just like some other object oriented languages, variables are 'pass by copy' and objects are 'pass by reference'. For objects to persist (and not be garbage collected) they must have some reference (including indirect) from the global object. Most of the functions so far have returned values, allowing the garbage collector to zap the 'execution object' from the heap.

To create a function with state, we need to return a reference to a method of an 'execution object'. This is easier to do than it sounds. Since javscript functions are data types, we can return a function from a function call. Essentially what we do is define an outer function to hold our state. Then we create a nested function (the code we want to operate on the state). This nested function/closure becomes a property of the parent's 'execution object' when the parent function is executed. If we return this function/method, we now have a way to create a function with state.

function StateHolder() {
    var count = 0; /* private property of 'StateHolder'*/
   /* when StateHolder is executed, CountFunc 
      becomes a property/method of StateHolder's 
      execution object*/
   function CountFunc() {
      count++; /*accessing the private var of StateHolder*/
      var countString = "function called: "+count+" times";
      return countString;
   }
   return CountFunc; /*return a reference to the method*/
}
/*assign the returned function, becomes a method of
   the 'global' object*/
var newFunction = StateHolder();
alert(newFunction());
alert(newFunction());
alert(newFunction());
alert(newFunction());

The results of this code is

function called: 1 times
function called: 2 times
function called: 3 times
function called: 4 times

You can see that state was maintained between function calls. By keeping a reference to a method attached to the 'execution object' created for executing "StateHolder" we've created a persistent object that allows something approaching "instance" variables. You may begin to see why this is important in the context of more traditional object oriented languages. This will be touched upon again in later articles.

Next Episode