10 Replies Latest reply on Apr 15, 2010 2:39 PM by Nyeski

    Closure doesn't retain variables from time of creation

    jsd99 Level 3

      In this code:

       

          for (var i:int = 0; i < 4; i++) {
                          var l:LinkButton = new LinkButton();

      [.. stuff ..]
                          l.addEventListener(MouseEvent.CLICK, function(m:MouseEvent):void {

                                 trace("button " + i);
                          });

      }

       

      When I click on the LinkButton, it always thinks i = 4.  How do I get it to retain the value of i at the time the closure is created?

        • 1. Re: Closure doesn't retain variables from time of creation
          David_F57 Level 5

          HI, not sure if this is right but I think it will always point to the last value of i which when the loop completes is 4.

           

          I would do it this way and use a switch statement for the button.

           

          protected function application1_creationCompleteHandler(event:FlexEvent):void

          {

          for (var i:int=0; i < 4; i++)

          {

          var tmpbtn:Button = new Button();

          tmpbtn.label = String(i);

          tmpbtn.name = "btn"+String(i);

          tmpbtn.addEventListener(MouseEvent.CLICK,onClick);

          vg.addElement(tmpbtn);

          }

          }

           

          private function onClick(event:MouseEvent):void

          {

               //trace(event.currentTarget.name);

             switch(event.currentTarget.name)

               {

               case "btn1": do what ever

               case "btn2": do what ever

               etc........

               }

          }

          David.

          • 2. Re: Closure doesn't retain variables from time of creation
            skider Level 1

            You're not actually creating a closure at the time you define the anonymous function. If you're familiar with other types of ECMAScript closures (e.g. Javascript closures), Actionscript follows the same pattern. Closures are created at the end of the function call. Because the value of i at return time is 4, the values for i in all the closures that you create are 4.

             

            You can solve this by creating a seperate function that's responsible for creating the closure. For example:

             

            public function originalClass

            {

                  for (var i:int = 0; i < 4; i++) {

                       var  l:LinkButton = new LinkButton();

                       [.. stuff ..]

                       l.addEventListener(MouseEvent.CLICK, createClickClosure(i));

                  }

            }

             

            private function createClickClosure(i:int):Function

            {

                 return function(m:MouseEvent):void {

                     trace("button " + i);

                 };

            }

            • 3. Re: Closure doesn't retain variables from time of creation
              levancho Level 3

              you can also put that createlosure within your function so that it does not polute global scope, especially if its not needed there.

               

              public function originalClass

              {

                     var  createClickClosure:Function =  function(i:int):Function
                          {
                         
                               return function(m:MouseEvent):void {
                         
                                   trace("button " + i);
                         
                               };
                         
                          }

               

                   for (var i:int = 0; i < 4; i++) {

                         var  l:LinkButton = new LinkButton();

                         [.. stuff ..]

                         l.addEventListener(MouseEvent.CLICK, createClickClosure(i));

                   }

              }

               

              what really matters is time of execution of the function that will encapsulate the closing variable.

              • 4. Re: Closure doesn't retain variables from time of creation
                jsd99 Level 3

                Thanks for the replies.  It's kind of a shame that these aren't "real" closures.  The workarounds are kludgey and offend my aesthetic sensibilities

                 

                Still, I've got it working now and results are what matter.

                 

                -jsd-

                • 5. Re: Closure doesn't retain variables from time of creation
                  skider Level 1

                  These are real closures, it's just that anonymous functions aren't the same things as closures. It just so happens that you need to use a second function to create a closure in ECMAScript.

                   

                  Whether or not this is an annoying way of handling things is an entirely different matter, but saying that it's not a real closure is simply false.

                  • 6. Re: Closure doesn't retain variables from time of creation
                    msakrejda Level 4

                    >You're not actually creating a closure at the time you define the  anonymous function.

                     

                    I don't think that's the case. The problem is that you're actually *closing over* a reference to that variable (not the value of the variable at the particular time):

                     

                                private function testClosure():void {
                                    var foo:int = 0;
                                    var f1:Function = function():void {
                                        trace(foo);
                                    };
                                    f1();
                                    foo += 5;
                                    var f2:Function = function():void {
                                        trace(foo);
                                    };
                                    f1();
                                    f2();
                                }

                     

                    You'll see the following output:

                     

                    0

                    5

                    5

                     

                    The first closure "sees" the changes to foo made after the function has been defined. The workarounds listed still apply, but for a different reason.

                    • 7. Re: Closure doesn't retain variables from time of creation
                      msakrejda Level 4

                      levancho wrote:

                       

                      you can also put that createlosure within your function so that it does not polute global scope, especially if its not needed there.

                       

                      public function originalClass

                      {

                             var  createClickClosure:Function =  function(i:int):Function
                                  {
                                 
                                       return function(m:MouseEvent):void {
                                 
                                           trace("button " + i);
                                 
                                       };
                                 
                                  }

                      }

                      Or, more tersely:


                      public function originalClass {

                        function createClosure(i:int):Function {

                          return function(m:MouseEvent):void {

                            trace("button " + i);

                          }

                        }  

                      }

                       

                      (you can use 'function' more or less like 'var' in this case).

                      • 8. Re: Closure doesn't retain variables from time of creation
                        levancho Level 3

                        Hi

                         

                        this has become an interesting topic,

                        from your example above it does not really show anything but little  different example that conceptually matches with original problem,

                        loop  variable overwriting a parameter to closure function which was not  executed yet,

                         

                         

                        my question is

                         

                        when you say:

                        ... but for a different reason.

                         

                        can you share what are those different reasons,

                         

                         

                         

                        IMHO, its all about timing when closure funciton is executed,

                         

                        and to presev the variable following example can be used:

                         

                        private function testClosure():void {
                                        var foo:int = 0;
                                        var f1:Function = function():void {
                                            trace("foo1 is " +foo);
                                        };
                                         var f2:Function = function(innerFoo:String):Function {
                                          return function():void {
                                             trace("foo3 is " + innerFoo);
                                         };
                                        };
                                       
                                        var f3:Function = f2(foo);
                                        f3();
                                       
                                        f1();
                                        foo += 5;
                                        var f4:Function = function():void {
                                            trace("foo4 is " +foo);
                                        };
                                        f1();
                                        f4();
                                        f3();

                         

                                    }

                        which basically is same as above suggestions to original question.

                         

                        and regarding declaration, I personally think

                         

                        var  createClickClosure:Function =  function  () {} ....

                         

                        is cleaner then having function myfunction () {}

                        nested withing another function ....


                        its just looks cleaner with variable to me.

                        • 9. Re: Closure doesn't retain variables from time of creation
                          msakrejda Level 4

                          my question is

                           

                          when you say:

                          ... but for a different reason.

                           

                          can you share what are those different reasons,

                          Sure. Skider stated that the reason for this behavior is that closures aren't created until the end of the function. I think my example shows that's not the case, because otherwise, the output would be

                           

                          5

                          5

                          5

                           

                          since foo is '5' at the end (unless I misunderstood what Skider was saying). You're right--it is about the timing of the execution and what the environment you close over (in this case, the variable reference 'foo') looks like *at the time of closure execution*--not at the time of closure definition.

                           

                          When you define another wrapper function, as in your example, you're closing over that function's argument, which will not change in this case, since it only lives in the scope of the f2 function.

                          • 10. Re: Closure doesn't retain variables from time of creation
                            Nyeski

                            This is Skider, from my non-work account.

                             

                            You are entirely right. My mistake. The reason that i is 4 each time that it's called is because it's called after the function's execution finishes, and at that time the value is 4. Please excuse my temporary insanity.