views:

322

answers:

10

In trying to learn JavaScript closures, I've confused myself a bit.

From what I've gathered over the web, a closure is...

Declaring a function within another function, and that inner function has access to its parent function's variables, even after that parent function has returned.

Here is a small sample of script from a recent project. It allows text in a div to be scrolled up and down by buttons.

var pageScroll = (function() {

    var $page,
        $next,
        $prev,
        canScroll = true,
        textHeight,
        scrollHeight;

    var init = function() {

        $page = $('#secondary-page');

        // reset text 
        $page.scrollTop(0);

        textHeight = $page.outerHeight();

        scrollHeight = $page.attr('scrollHeight');

        if (textHeight === scrollHeight) { // not enough text to scroll

            return false;    

        };

        $page.after('<div id="page-controls"><button id="page-prev">prev</button><button id="page-next">next</button></div>');

        $next = $('#page-next');

        $prev = $('#page-prev');

        $prev.hide();

        $next.click(scrollDown);

        $prev.click(scrollUp);

    };

    var scrollDown = function() {

        if ( ! canScroll) return;

        canScroll = false;

        var scrollTop = $page.scrollTop();

        $prev.fadeIn(500);

        if (scrollTop == textHeight) { // can we scroll any lower?

            $next.fadeOut(500);

        }

        $page.animate({ scrollTop: '+=' + textHeight + 'px'}, 500, function() {

            canScroll = true;

        });    

    };

    var scrollUp = function() {

        $next.fadeIn(500);

        $prev.fadeOut(500);

        $page.animate({ scrollTop: 0}, 500);    


    };

    $(document).ready(init);

}());

Does this example use closures? I know it has functions within functions, but is there a case where the outer variables being preserved is being used?

Am I using them without knowing it?

Thanks

Update

Would this make a closure if I placed this beneath the $(document).ready(init); statement?

return {
    scrollDown: scrollDown
};

Could it then be, if I wanted to make the text scroll down from anywhere else in JavaScript, I could do

pageScroll.scrollDown();

Another Update

Wow, looks like that worked! Here is the code on JSbin. Note the page scroller doesn't exactly work in this example (it doesn't seem to go to the bottom of the text), but that's OK :) Bonus points for anyone that can tell me why it's not scrolling to the bottom.

A: 

Yeah, sure there are closures about. For example, you use the $page, $next etc. declared in a scope outside for example init (which uses them).

npup
+1  A: 

I'm no expert on Javascript, but from what I read here, yes, you have closures.

Note that all of the inner functions share the variable $page, for example. init assigns to it, and later the scrolling functions update it. So in principle the inner functions are closures of the outer function. The outer function passes init to document.ready, so in some Javascript-ish way, it returns a closure.

That said, I also have to say that this code is a very bad example of functional programming, since that pure functions are not supposed to have side-effects (like assigning to a shared variable). Nevertheless, it has closures.

Little Bobby Tables
+2  A: 

I don't think you're using any closures, because you're not returning the inner functions from your outer function, so those variables will always be in scope when they're called. In other words, init(), scrollDown() and scrollUp(), have to be called from within pageScroll(). If pageScroll() returned any of its three inner functions, and then you called them outside of pageScroll(), the use of the $page, $next, $prev, etc. variables would be closures. I think.

Hooray Im Helping
I love your display name and Gravatar :D
alex
A: 

What a day not to bring my Javascript: The Good Parts book to work...

I believe the short answer is "No", because of this part of the definition: "even after that parent function has returned." But I'm not 100% sure.

This may help, though.

YouTube clip of Javascript: The Good Parts

http://www.youtube.com/watch?v=hQVTIJBZook&amp;feature=player_embedded

RussellUresti
Does my update change your answer at all?
alex
A: 

You are attaching to the onclick event of #page-next element a function scrollDown that keeps a reference to the variable canScroll of your object pageScroll. So yes, your are using closures.

In fact, there are 4 closures in your example : the function sent to $next.click, the one sent to $prev.click, the one sent to $page.animate, and the one sent to $(document).ready.

Alsciende
A: 

Yes you do have a closures in there, look at this quick example

         <script type="text/javascript">
    var t1=null;
    function test(){
        alert('Test');
        var x  = 10;
        t1 =  function(){
            alert('x='+ x);
        };  
    };
    function test2(){
        t1();
    }
    </script>
    <a href="javascript:test();">Test me 1 </a>  

<a href="javascript:test2();">Test me 2</a>

Closures have access to its parent variables in this case our function 't1' accesses privately declared var x; also as you stated 'inner function has access to its parent function's variables, even after that parent function has returned.' when you click 'Test me 2' you will get an alert with x=10 even those that the function test() have already returned, does this make sense.

Greg
A: 

Yes, there are a number of closures in the sample code. The confusion may come from applying too strict a requirement on closures from your definition. While a closure does have access to its bound free variables for the lifetime of the closure, it is not necessary for the closure to extend the lifetime of those variables beyond their normal environment for the function to be considered a closure.

For example, let us say we have a function findAllPeople that finds in a list all of the people that have a first name that is supplied as a parameter to the function. We could create an inner function that takes a person and tests their name against the supplied name. That inner function would have two free variables--namely, the person and the name to test against. The name to test against is bound to the parameter supplied to findAllPeople. Hence, the inner function is closed over that free variable. The fact that the inner function is created, used, and discarded all while executing findAllPeople and is not used anywhere outside of findAllPeople does not have any bearing on the fact that the inner function is a closure.

Matthew T. Staebler
A: 

obviously yes... when u nested a function inside another function,u have a closure.. thats it

var func = (function(){
             var txt = 'Hi There!'
             return function(){
               alert(txt);
             }
           })();

 func(); //alert 'Hi There', eventhough the outter function has exit
slier
ofccourse its a closurewhen u run func() it will alert 'hi there'how come it alert 'hi there' if there is no closure
slier
+1  A: 

Take a look at Dmitry A. Soshnikov's blog series about ECMA-262 ('javascript').

Post 6 http://dmitrysoshnikov.com/ecmascript/chapter-6-closures/ is about closures, and if you read the previous posts you should get quite a good grasp on it.

The short story is; function declarations, or function expressions placed inside a function(declaration or expression) will have the parent functions scope added to its scope chain. Therefor it has access to all the variables contained in every function up the scope chain (ending with the global scope). But so far we are not talking about a closure.

The concept of a 'closure' appears when you create a reference to such a contained function outside the scope chain, either by returning a reference to the function, or by adding a reference to it as a property on an object higher up in the scope chain. [so that you by calling the function (by that reference) can modify variables hidden inside a non-accessible scope.]

This reference will also cause all variables contained in said scopechain to have at least one reference (from each scope) meaning that none of the variables present in the scope chain can be garbage collected. This is a so called memory leak (unless you are actually planning to use the variables that is!).

Sean Kinsey
+9  A: 

Any time you define a function, that function will "enclose" the scope chain it was defined in. When that function is executed that scope chain is available in its body. When a function is defined inside another function, the scope chain includes:

  • The parent function's locally defined variables
  • The parent function's arguments
  • The parent function's parent function's local vars and arguments, etc..
  • Global variables

The real value of closures is to return an inner function that has captured an enclosed variable that isn't going to change, like so:

        function test (value){
            return function(){
                alert(value); //using enclosed argument
            }
        }

        var t1 = test("first");
        var t2 = test("second");
        t1();//alerts "first"
        t2();//alerts "second"

One danger of closures is that they enclose a reference to the enclosed properties, not a snapshot of their values, so watch out for creating an enclosing function inside of a loop like so:

function makeArray (){
    var result=[]
    for (var i =0; i < 3; ++i){
        result.push(function(){
            alert("function #" +i); //using enclosed i
        })
    }
    return result;
}

var fnArray =makeArray();
fnArray[0]();//alerts "function #3"
fnArray[1]();//alerts "function #3"
fnArray[2]();//alerts "function #3"

In a loop you need to find a way to make your enclosed variables unchanging. Here is an example of using a nested closure to copy the re-used counter to a single-use argument:

function makeArray (){
    var result=[]
    var wrapFunction=function (counter){
        return  function(){
            alert("function #" +counter); //using enclosed counter
        }
    }
    for (var i =0; i < 3; ++i){
        result.push(wrapFunction(i))
    }
    return result;
}

var fnArray =makeArray();
fnArray[0]();//alerts "function #0"
fnArray[1]();//alerts "function #1"
fnArray[2]();//alerts "function #2"
Mark Porter
+1 for explanation
thomask
Great explanation.
Hooray Im Helping