CodingBison

JavaScript supports two levels of scope: global and function-level. When we execute a function, it creates a new scope in addition to the global scope. In this function-level scope, all global variables are accessible. However, variables declared within a function are not visible outside that function.

Not all variables defined within a function have function-level scope. It is only those variables that are defined with the var keyword within functions that have a function-level scope. If we forget to add the "var" keyword, then the variable automatically becomes a global variable, even though it is defined within a function.

Since it is possible that a programmer can name a global variable same as that of an existing one, it is a good idea to reduce the number of global variables to as minimum as possible. Also, since the default scope of a variable is global (as noted above, if we fail to add "var" to a variable inside a function), this automatically leads to addition of a lot more variables in the global scope. Thus, failing to add the "var" keyword can increase the chances of collisions of variable names. It is due to these collisions that a lot of programmers generally regard global variables as evil!

In this section, we present two examples that highlight the behavior of function-level scope.

We begin with an example that defines a handful of global variables (varGlobal, elem, and str), a function-level (varFunctionLevel), and an un-intended global variable (varBadFunctionLevel) since we intentionally skip the "var" keyword. The example shows that we can access a global variable from within a function and can also update its value. The second variable, varFunctionLevel has a scope that is limited only to the function and once we are outside the function, this variable ceases to exist. However, since varBadFunctionLevel acquires the default global scope, we are able to access it outside the function.

 <!doctype html>
 <html>
 <div id="idDiv"></div>
 <script type="text/javascript"> 
 // Get a handle of the div element.
 var elem = document.getElementById("idDiv");

 // Define global variables. 
 var varGlobal = "Dragon";
 var str = "[within outer function] ";

 // Define a function and invoke it. 
 (function funcDummy() {
     // Define a function-level variable. 
     var varFunctionLevel = "The Lord of the Rings";

     // Define a function-level variable but without the "var" keyword.
     varBadFunctionLevel = "The Dark Lord";

     // Update the global variable. 
     varGlobal = "Centaur"; 

     elem.innerHTML += str + "Global Variable is: " + varGlobal;

     // Define a inner function and invoke it. 
     (function innerFuncDummy() {
         str = "<br>[within inner function] ";
         elem.innerHTML += str + "Global Variable is: " + varGlobal;
         elem.innerHTML += str + "Function Variable is: " + varFunctionLevel;
     })();
 })();

 str = "<br>[outside of functions] ";
 elem.innerHTML += "<br><br>After invoking the function: " 
 elem.innerHTML += str + "Global Variable is: " + varGlobal;

 // Check if we can access a function-level variable outside the function.
 if (window.varFunctionLevel == undefined) {
     elem.innerHTML += str + "varFunctionLevel is not defined.";
 }
 if (window.varBadFunctionLevel != undefined) {
     elem.innerHTML += str + "varBadFunctionLevel has become global.";
 }
 </script>
 </html>

Note that to refer to the variable varFunctionLevel and check if it is defined in the global context, we use the "window" object. The window object is the global object and all variables belonging to the global scope are part of this object. Thus, we can refer to the varGlobal as window.varGlobal as well.

Here is the output when we load the above file in a browser:



Figure: Contrasting Function-level Scope and Global Scope.

The above output also confirms that we must provide the "var" keyword when defining a function-level variable. If we forget to do so, then the variable automatically acquires a global variable. Thus, without the "var" keyword, the variable varBadFunctionLevel becomes a global variable.

Sharing "this" variable with an inner function

Though the inner function has access to variables defined by the outer function or global variables, the inner function does not have access to all properties of the outer function. There are two exceptions. First, is the arguments objects; JavaScript functions use an object (named "arguments") to store information about passed arguments. Needless to say that since both functions can have different arguments and so they cannot share the arguments object. Second, for a special case when the outer function is invoked in the context of an object (either as a method of an object or as a Constructor), the inner function does not share the "this" value of the outer function.

Let us discuss the case of sharing the "this" value a little bit more. By default, the "this" keyword in functions (outer or inner) points to the global object. The exception to this is when the outer function is invoked as part of an object (Constructor or a simple assignment as above). For these cases, the "this" keyword points to the global object for the inner function. Hence, for this particular case, the value of the "this" keyword is different for the outer and the inner functions.

Let us understand this using an example.

In this example (provided below), when the "this" keyword refers to a non-global object (objDummy, in this case), then the inner function, innerFuncDummy() does not share "this" keyword with the outer function. To make the "this" keyword point to a non-global object, we therefore, intentionally keep the outer function a method of an object, objDummy. Therefore, in the following example, when we print this.magicTale, it prints "The Lord of the Rings" from the outer function but it prints "undefined" from the inner function.

 <!doctype html>
 <html>
 <div id="idDiv"></div>

 <script type="text/javascript"> 
 // Get a handle of the div element.
 var elem = document.getElementById("idDiv");

 // Define an object.
 var objDummy = {
     magicTale: "The Lord of the Rings",
     funcDummy: function(magicBeing) {
         elem.innerHTML += this.magicTale;     // Prints "The Lord of the Rings".

         // Define an inner function and invoke it.
         (function innerFuncDummy() {
             elem.innerHTML += this.magicTale; // Prints "undefined".
         })();
     }
 };

 // Invoke the object's method.
 objDummy.funcDummy();
 </script>
 </html>

To avoid this discrepancy, we can use a simple (and popular) hack, where we copy the "this" keyword to a variable "that" and then refer to the "that" variable in the inner function!

 <!doctype html>
 <html>
 <div id="idDiv"></div>

 <script type="text/javascript"> 
 // Get a handle of the div element.
 var elem = document.getElementById("idDiv");

 // Define an object.
 var objDummy = {
     magicTale: "The Lord of the Rings",
     funcDummy: function(magicBeing) {
         var that = this;
         elem.innerHTML += this.magicTale;     // Prints "The Lord of the Rings".

         // Define an inner function and invoke it.
         (function innerFuncDummy() {
             elem.innerHTML += that.magicTale; // Prints "The Lord of the Rings". 
         })();
     }
 };

 // Invoke the object's method.
 objDummy.funcDummy();
 </script>
 </html>




comments powered by Disqus