Freigeben über


Scope chain of JScript Functions

 

Scope chain of JScript functions is something programmers find it difficult to understand easily. This is because it is different from the scope chain of functions in programming languages like C, C++. Discussed below are the details of scope chain of JScript functions, the understanding of which will help in avoiding problems faced when handling closures.

 

Functions in JScript help perform a set of operations at single invocation. There are different ways in which you can define a function in JScript, let us see them below.

 

Function statement:

function maximum(x,y)

{

    if(x > y)

        return x;

    else

        return y;

}

             

maximum(5,6) //Returns 6;

This type of syntax is used more commonly for defining top level JScript functions.

 

Function Literals:

var obj = new Object();

obj.maximum = function (x,y) {

                if(x > y)

                    return x;

                else

                    return y;

              };

             

obj.maximum(5,6) //Returns 6;

 

This type of syntax is more commonly used for defining functions as methods of objects.

 

Function Constructor:

var maximum = new Function ("x","y","if(x > y) return x; else return y;");

maximum(5,6); //Returns 6;

Defining functions in this form is difficult to type and is used less frequently.

 

Function Definition:

 

Function definition refers to creation of function object within the JScript engine. This object is added as property to the global object (for top level functions) or to the activation object(for nested functions) by the given function name. Interesting part is thatfunction definition for top level functions happen first when script execution starts. For the example given below once the JScript engine completes parsing the script and starts to execute the script, it creates function object for ‘func’ and adds it as property to the global object.

 

/*function 'func' can be invoked here as the defintion of 'func' has already occured when execution of script starts.*/

WScript.echo(func(2)); //Returns 8

//Execution of this statement overwrites the value of 'func' to 'true'.

var func = true;

WScript.echo(func); //prints true;

/*Parsing of the below statement causes the definition of function object for ‘func’ when script execution starts.*/

function func(x)

{

      return x * x * x;

}

 

 

In the case of nested function shown below, definition of inner function occurs at the time of invocation of the outer function and added as property to the activation object of outer function.

function outerFn()

{

    function innerFn()

    {

   

    }

}

outerFn(); //Invocation of outerFn causes defintion of innerFn when

      //the body of outerFn starts to execute

 

 

Note: For Function Constructors, function definition occurs at the time of invocation of ‘Function’ constructor.

 

Scope chain:

               

Scope chain refers to the chain of objects whose properties are looked for when a variable is looked for its existence and its value. Functions in JScript have their scope chain saved when they are defined. It is this scope chain in which they execute rather than the scope chain from which they are invoked. This is of particular importance when dealing with execution of nested functions.

function outerFn(i)

{

    return function innerFn()

    {

        return i;

    }

}

var innerFn = outerFn(4);

innerFn();//Returns 4

 

When innerFn is invoked, the variable ‘i’ referenced inside is neither defined locally nor is it defined in the global object. So how does variable ‘i’ get resolved? You might think of the scope chain to consist of the activation object for innerFn followed by the global object. But this is not the case with execution of nested functions. Let us start by analyzing the scope chain for top level functions followed by nested functions.

 

Global functions:

The execution scope chain of top level functions is pretty straight forward.

var x = 10;

var y = 0;

function testFn(i)

{

    var x = true;

    y = y + 1;

    WScript.echo(i);

}

testFn(10);

 

 

 

                                                                               

Figure 1: Scope chain of global functions

Global Object: The JScript engine creates an object (Global object) when executing of JScript code starts and initializes it with properties referring to predefined values like ‘Infinity’, ‘Math’. The global variables defined in the script are nothing but properties getting defined on this global object.

Activation Object: When the JScript engine invokes any function, it creates a new object (Activation object) and all the local variable defined within the function and the named argument passed to the function along with ‘arguments’ object get defined as properties of this activation object. The activation object is also added to the front of the execution scope chain.

Nested functions:

The interesting part is the way in which the execution scope chain gets built for nested functions. This is of significant importance when nested functions form closures. Let us analyze the JScript code below.

function outerFn(i,j)

{

    var x = i + j;

    return function innerFn(x)

    {

        return i + x;

    }

}

var func1 = outerFn(5,6);

var func2 = outerFn(10,20);

WScript.echo(func1(10));//Returns 15

WScript.echo(func2(10));//Returns 20

 

You can observe different values of ‘i’ used within innerFn when invoking func1(10) and func2(10). How does this happen? This can be clear once we understand how the variable ‘i’ used within innerFn gets resolved?

 

Let us see statement,

var func1 = outerFn(5,6);

 

The invocation of outerFn (5, 6) defines a new function object for innerFn and gets added as property to the activation object of outerFn. The execution scope chain at this point consists of the activation object of outerFn and the global object. This scope chain gets saved in the function object for innerFn. Finally we return this function and reference as ‘func1’.

 

WScript.echo(func1(10));//Returns 15

 

When ‘func1’ is invoked, its own activation object is created and added to the front of the saved scope chain. It is this scope chain under which the execution of ‘func1’ happens. From this scope chain you can see how the variable ‘i’ gets resolved to the property ‘i’ in the activation object of the invocation of outerFn(5,6).The snap shot below shows the whole process discussed so far.

                                                                                               

 

 

Figure 2: Scope chain of nested functions (func1)

Now let us come back to the question of how different values of ‘i’ were used when invoking func1(10), func2(10)? Let me show the snap shot for func2, as you observe different activation objects of outerFn are saved in the scope chain of func1 and func2.

 

           

                                                                                                                                                                      

Figure 3: Scope chain of nested functions (func2)

 

 

 

Now arises the questions of, won’t an activation object be created when a function is invoked and get destroyed when the function returns? This can be better understood by considering three cases.

 

i) Functions without nested functions

function outerFn(x)

{

   return x*x;

}

var y = outerFn(2);

 

 

In the case of functions without nested functions, the activation object created when invoking the function is attached to the front of the execution scope chain and the function execution proceeds. The scope chain is the only place where the activation object is being referenced. When the function exits, the activation object is removed from the execution scope chain and since no one else is referencing the activation object, it gets garbage collected.

 

ii) Functions with nested functions not referenced outside their enclosing function

function outerFn(x)

{

    //No reference for 'square' outside 'outerFn'

    function square(x)

    {

        return x*x;

    }

     

    //No reference for 'cube' outside 'outerFn'

    function cube(x)

    {

        return x*x*x;

    }

    var temp = square(x);

    return temp/2;

}

var y = outerFn(5);

 

 

In the case of functions with nested functions not referenced outside the enclosing function, the activation object created when invoking the function is not only attached to the front of the execution scope chain but also reference in the saved scope chain of the nested functions. But when the function exits, the activation object is removed from the execution scope chain and since both the activation object and the nested function object are referencing each other with no external reference for either of them both the objects get garbage collected. The activation object of outerFn references nested functions objects square and cube, nested function objects square and cube reference activation object of outerFn in their saved scope chain.

 

iii) Functions with nested functions referenced outside their enclosing function

Example 1:

function outerFn(x)

{

    //Inner function referenced outside through return

   //value of 'outerFn'

       return function innerFn()

       {

           return x*x;

       }

   }

   //Reference to inner function returned.

   var square = outerFn(5);

square();

Example 2:

var square;

function outerFn(x)

{

//Inner function referenced outside through global variable

//'square'

    square = function innerFn()

    {

        return x*x;

    }

}

outerFn(5);

square();

 

In the case of functions with nested functions referenced outside the enclosing function, the activation object created when invoking the function is not only attached to the front of the execution scope chain but also reference in the saved scope chain of the nested functions. When the outer function exits, even though its activation object is removed from the front of the execution scope chain it is still referenced through the saved scope chain of the nested functions. Since an external reference to the nested function exists, the activation object remains alive in the saved scope chain of the nested function and is accessed during the scope resolution when invoking the inner function.

     

Multiple Nested functions:

One interesting scenario that can happen when there are more than one nested functions is, all the nested functions will have the same activation object of the outer function in their scope chain and any changes on this activation object will be reflected across the nested functions.

function createCounter(i)

{

    function increment()

    { ++i; }

   

    function decrement()

    { --i; }

   

    function getValue()

    { return i; }

   

    function Counter(increment,decrement,getValue)

    {

        this.increment = increment;

        this.decrement = decrement;

        this.getValue = getValue;

    }

   

    return new Counter(increment,decrement,getValue);

}

var counter = createCounter(5);

counter.increment();

WScript.echo(counter.getValue());//Returns 6

 

 

 

 

 

 

 

Figure4: Scope chain of multiple nested functions

 

The above snap shot shows the activation object of createCounter is common in the scope chain of all the nested function calls.

 

Closures and Circular references:

 

Having discussed about scope chain of JScript functions, I would like to bring to notice the circular reference memory leaks caused due to closures. Closures commonly refer to nested functions which can be invoked from outside the enclosing function. Let me give a simple example below,

function outerFn(x)

{

    x.func = function innerFn()

             {

             }

}

var div = document.createElement("DIV");

outerFn(div);

 

 

In the above example you can observe circular reference getting created between DOM object and JScript object. DOM object div has a reference to the inner function object innerFn through ‘func’. The inner function innerFn has reference to the DOM object div through ‘x’ which is saved in the activation object of the invocation to ‘outerFn(div)’ which remains alive in the saved scope chain of innerFn. You can read more about memory leaks due to circular reference from the MSDN article.

 

Thanks,

 

P.Shivaram

SDET

Comments

  • Anonymous
    July 28, 2007
    A very detailed article. This really helped me clarify some doubts. One quick question, does all of it apply to JavaScript in general or some of it is specific to Microsoft's implementation of JavaScript? Lastly, I expect to see few more of this knowledge based article in future.

  • Anonymous
    August 05, 2007
    Thanks for your comments. You can expect more such articles in future. All the scope chain stuff explained in this article is in general.

  • Anonymous
    January 19, 2008
    very detailed article. This really helped me clarify some doubts. One quick question, does all of it apply to JavaScript in general or some of it is specific to Microsoft's implementation of JavaScript? Lastly, I expect to see few more of this knowledge based article in future.

  • Anonymous
    January 23, 2008
    The comment has been removed

  • Anonymous
    January 24, 2008
    Você é bem-vindo (You are welcome)

  • Anonymous
    March 12, 2008
    The comment has been removed

  • Anonymous
    March 16, 2008
    It's a shame that I can't see the image.

  • Anonymous
    May 05, 2009
    Nice explanation. Thanks a lot.

  • Anonymous
    January 23, 2010
    I'm wondering about JScript.NET private variables. Please take a look on the following code: import System; import System.Windows.Forms; import System.Drawing; var jsPDF = function(){ var state = 0; var beginPage = function(){ state = 2; out('beginPage'); } var out = function(text){ if(state == 2){ var st = 3; } MessageBox.Show(text + ' ' + state); } var addHeader = function(){ out('header'); } return { endDocument: function(){ state = 1; addHeader(); out('endDocument'); }, beginDocument: function(){ beginPage(); } } } var j = new jsPDF(); j.beginDocument(); j.endDocument(); Output: beginPage 2 header 2 endDocument 2 if I run the same script in any browser, the output is: beginPage 2 header 1 endDocument 1 Why it is so?? Thanks, Paul.

  • Anonymous
    April 11, 2010
    Hey, I like the article, but I would appreciate if you could clarify the following. So, is there a two way link between the activation objects and the global object? There has to be a link(pointer) from the activation objects towards the global object for scope resolution. But is there also a link the other way around? Because, you mentioned, when function returns, the global object doesn't point to the activation object anymore and therefore it is garbage collected. Also, does this also mean that any object that doesn't have a link(pointer) from the global object is garbage collected? I would appreciate if you could help clarify these doubts. Thanks again!