Closure On JavaScript Closures

Tuesday, July 10, 2007

At the end of the last post, we looked at a function named createDelegate. The createDelegate function constructed a nested function named shim, and shim created all the magic we needed to invoke an object's method during a button click event. In this post we'll finally put the topic to rest.

First, we have to cover an important topic: scope.

Scope

A variable's scope defines where the variable can be used. In JavaScript there is really only local scope and global scope. Notice that local variables will hide global variables of the same name.

var x = 10;

function f()
{    
    {
        
// no block scope in ECMAScript -
        // this x is available everywhere inside f()
        var x = 20;    
    }

    alert(x);
}

f();      
// this displays 20
alert(x);  // this displays 10

Nesting a function inside a function is essentially nesting a local scope inside a local scope. Note that functions have access to all the variables and arguments in all their ancestor's scopes (as long as the variables aren't hidden by a local variable).

var x = 10;

function f()
{    
    
var y = 15;
    
    
function g()
    {
        
var z = 25;
        alert(x+y+z);    
    }    
    g();
}

f();
// this displays 50

The above behavior is what we expect after reading section 10 of the ECMAScript standard. JavaScript creates a new activation object for each scope. The activation object references all function arguments and variables in the scope. The activation objects become part of a scope chain that JavaScript will traverse when trying to find a variable. The following diagram is a conceptual model of all these players, with red brackets indicating scope, red circles indicating activation objects, and blue lines indicating the scope chain.

When we ask for the value of x inside function g(), JavaScript looks at activation object for the innermost scope, but doesn't find x, so it moves to the parent scope, but still doesn't find x. Finally, the runtime looks at the global scope and finds x. If the engine had found x in a nested scope, it wouldn't have gotten to the x in the global scope.

Knowing what we know now, what does the following code display?

var x = 10;

function printIt(y)
{
    
function doAlert()
    {
        alert(x+y)
    }
    
    doAlert();
}

printIt(5);

The answer is 15. The innermost function has access to both the incoming y parameter of printIt and the global variable x thanks to the scope chain. Now, let's add a twist.

Closures

Instead of having the printIt function invoke doAlert, let's return doAlert as an object and execute the function from global scope.

var x = 10;
var y = 20;

function makePrintIt(y)
{
    
return function doAlert()
    {
        alert(x+y)
    }      
}

var f = makePrintIt(5);
f();

Returning a function object creates a closure, which Wikipedia defines as "a function paired with an environment". Generally speaking, when a function like makePrintIt() exits, all the local variables and arguments are lost. A closure, however, closes over its environment and keeps these local variables and arguments alive. The function captures its execution environment.

Looking at our conceptual diagram above, it's easy to see how a JavaScript engine might implement a closure*. All a nested function has to do is carry a reference to its activation object, which will keep all the variables and arguments in the scope chain alive.

If you've gotten a good grip on closures, then you'll realize the last code snippet will display 15. Since doAlert captured its execution environment, it will use the y value of 5 that was passed into makePrintIt, not the y value of 20 that exists at global scope. Thanks to closures, JavaScript functions always execute with their lexical scope (where they appear in the source code).

Finally, every call into makePrintIt() creates a new closure by pairing a function object with its unique execution environment. That means the following code prints 10, then 15.

var x = 5;

function makePrintIt(y)
{
    
return function doAlert()
    {
        alert(x+y)
    }      
}

var f1 = makePrintIt(5);
var f2 = makePrintIt(10);

f1();
f2();

With this understanding of closures, I hope you can look at the implementation of Function.createDelegate and understand what is happening!

* The ECMAScript standard only describes how the interpreter has to behave. How scope chains and closures are actually realized is an implementation detail.


Comments
Josh Stodola Friday, July 13, 2007
This was a very interesting read for me. Thanks Scott!
bw Monday, July 16, 2007
The Defintive Guid says"When a function is invoked, a call(activation) object is created for it and placed on the scope chain."
KES Friday, February 26, 2010
What results will if in last example change code to:
f1();
x= 10;
f2();

Results will: 10, 20
or
Results will: 10, 15
?
gravatar Jeroen De Dauw Tuesday, October 19, 2010
@KES: 10, 20

The only thing that's retained are local values and the activation object. x is not a local value, so it's not retained. If you want to get 10, 15, assign x to a new var in makePrintIt, and add that one to y instead of x.

Great post btw :)
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!