SitePoint Sponsor

User Tag List

Results 1 to 14 of 14
  1. #1
    Community Advisor silver trophybronze trophy
    dresden_phoenix's Avatar
    Join Date
    Jun 2008
    Location
    Madison, WI
    Posts
    2,734
    Mentioned
    31 Post(s)
    Tagged
    1 Thread(s)

    help, closure not closing... :p

    some times .js throws me for a loop (there is one for the PUN contest).

    I am trying to programmatically attach function calls to an object onclick event. Everything was fine except when I added the nested array loops. I have used var 'fn' to attempt to create a closure and store the value of 'fool ' at that point in the loop , so fn= foo[k] for all ks. but it always = the last value of foo[k]. I am sure I am missing something horrendously simple but I just cant see it at the moment and a fresh pair of eyes would be greatly appreciated.


    Code:
    function prepWidg(fix){
    			var sel=new Array('.wDel', '.wUp','.wDwn', '.wAdd');
    			var foo= new Array('delWidget', 'moveUp', 'moveDwn', 'newWidget');
     			for (k=0,ll=sel.length; k<ll; k++){		 	
    			    bType=fix.querySelectorAll(sel[k]);
    			    fool=foo[k];
      		 	 	for (i=0,l=bType.length;i<l; i++ ){
    			 	 	 bType[i].onclick=function(e){  
    			 	 	 	var fn=fool ; // currernt value of 'fool' should be stored  in this onclick funtion but it's nto so :(
    			 	 	 	var self=this; 
    			 	 	 	alert(fn); 
    			 	 	 	master[fn](self); 
    			 	 	 	return false; 
    			 	 	 }
    			 	}
    		 	}
     		 	return fix;
    		}

  2. #2
    SitePoint Mentor bronze trophy
    fretburner's Avatar
    Join Date
    Apr 2013
    Location
    Brazil
    Posts
    1,257
    Mentioned
    32 Post(s)
    Tagged
    5 Thread(s)
    Morning dresden_phoenix,

    This code should give you the desired result:
    Code JavaScript:
    function createHandler(fn) {
        return function(e){
            e.preventDefault();
            var self = this; 
            alert(fn);
            master[fn](self);
        };
    };
    function prepWidg(fix){
        var sel = new Array('.wDel', '.wUp','.wDwn', '.wAdd');
        var foo = new Array('delWidget', 'moveUp', 'moveDwn', 'newWidget');
     
        for (k=0,ll=sel.length; k<ll; k++) {
            bType = fix.querySelectorAll(sel[k]);
            for (i=0,l=bType.length;i<l; i++ ) {
                bType[i].onclick = createHandler(foo[k]);
            }
        }
        return fix;
    }
    Last edited by fretburner; Jun 15, 2013 at 04:36. Reason: Removed redundant code

  3. #3
    SitePoint Mentor bronze trophy
    fretburner's Avatar
    Join Date
    Apr 2013
    Location
    Brazil
    Posts
    1,257
    Mentioned
    32 Post(s)
    Tagged
    5 Thread(s)
    Sorry for the brevity of my previous post, I was just on my way out this morning. I thought perhaps I should add some explanation as to why the solution I posted works and what was causing the issue with the original code, which might make the answer more useful to others.

    In the original code, when you assign the onclick functions you're forming a closure with the outer variables set within the prepWidg function which means that your onclick handlers can still access these variables any time they are called. The problem is that every time the outer loop runs, the value of fool changes. After the loop has ended, fool remains set to the last value ('newWidget') and so this is the value accessed by all the onclick handlers.

    The solution is to create a function (in this instance, createHandler) which takes the current value of foo[k] as an argument, and returns a closure with that value bound to fn.

  4. #4
    Community Advisor silver trophybronze trophy
    dresden_phoenix's Avatar
    Join Date
    Jun 2008
    Location
    Madison, WI
    Posts
    2,734
    Mentioned
    31 Post(s)
    Tagged
    1 Thread(s)
    fretburner thank you VERY MUCH for your reply and explanation. There is a concept I am still not clear on tho.
    so a loop creates/needs a closure just like a function? I thought the 'var fn' inside the anonymous function ( which works for implementations not involving loops) was supposed to do the same a s calling a named function.

  5. #5
    SitePoint Wizard bronze trophy Jeff Mott's Avatar
    Join Date
    Jul 2009
    Posts
    1,150
    Mentioned
    14 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by dresden_phoenix View Post
    I thought the 'var fn' inside the anonymous function ( which works for implementations not involving loops) was supposed to do the same a s calling a named function.
    Your click handler does indeed have access to the variable fool by closure. The problem is that the click handler doesn't execute right away, which also means var fn = fool; doesn't execute with each loop iteration. Instead, the handler is invoked at some later time when a click happens, and it's only at that point that var fn = fool; executes, but by then the loop has already run its course, and the value of fool has already changed.
    "First make it work. Then make it better."

  6. #6
    SitePoint Wizard bronze trophy Jeff Mott's Avatar
    Join Date
    Jul 2009
    Posts
    1,150
    Mentioned
    14 Post(s)
    Tagged
    0 Thread(s)
    fretburner's code above is a cleaner version of what I'm about to show below, but seeing it all inline might help.

    Code:
    function prepWidg(fix){
        var sel=new Array('.wDel', '.wUp','.wDwn', '.wAdd');
        var foo= new Array('delWidget', 'moveUp', 'moveDwn', 'newWidget');
        for (k=0,ll=sel.length; k<ll; k++){
            bType=fix.querySelectorAll(sel[k]);
            for (i=0,l=bType.length;i<l; i++ ){
                // immediately invoked function expression
                (function () {
                    // since this function executes immediately during each loop iteration, 
                    // it has access to the current value of "k"
                    var fn = foo[k];
                    // each time this function runs, a new and distinct "fn" variable is created
                    // this distinct "fn" will be available to any functions created within its execution context,
                    // such as this upcoming click handler
                    bType[i].onclick=function(e){
                       var self=this;
                       alert(fn);
                       master[fn](self);
                       return false;
                    }
                });
            }
        }
        return fix;
    }
    "First make it work. Then make it better."

  7. #7
    Community Advisor silver trophybronze trophy
    dresden_phoenix's Avatar
    Join Date
    Jun 2008
    Location
    Madison, WI
    Posts
    2,734
    Mentioned
    31 Post(s)
    Tagged
    1 Thread(s)
    Thanks Jeff.. It makes sense now; tho I cant say I am not disappointed , I had always assumed the handler function was created like any other function so I didn't need a self invoking foo to capture k ( in other words I thought the value was captured during the VAR declaration, the same way the magic constant 'this' is captured . But now that I know I can anticipate this behaviour in the future.

  8. #8
    SitePoint Wizard
    Join Date
    Mar 2001
    Posts
    3,537
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    so I didn't need a self invoking foo to capture k ( in other words I thought the value was captured during the VAR declaration, the same way the magic constant 'this' is captured .
    As Jeff Mott honed in on, no code inside a function ever executes until the function is called. Here is an example that I hope is not too insulting:

    myjs.js
    Code:
    function greet() {
        console.log("hello");
    }
    The confusion you are having with your code is actually the same as someone who asks, "How come I don't see any output in the console when I run myjs.js?? Hopefully, your inner self is shouting, "That code won't send anything to the console unless you add the line greet() !!"

    Similarly, your anonymous onclick function doesn't execute any of the code inside it until something is clicked. js does not care if there are 1,000 closures in your onclick function or if the end of the world is nigh--js will not under any circumstances even look at the code inside a function until you call the function. Okay, the parser does look inside the function to check for syntax errors and some other other things--but that's it. Don't believe me? Run this code:

    myjs.js
    Code:
    function greet() {
      var x = y/0;
    }
    No errors--yet clearly there are two errors: y does not exist, and there is a division by 0. So the question I pose to you is: if js won't execute that assignment **when the function is defined**, why do you think js is going to show your function special consideration and execute the assignment:

    Code:
    var fn = fool;
    when you define your onclick function?

    Only after something is clicked will the code inside your onclick function execute. At that time, your assignment will execute, and js will ask itself, "What is the value of fool? I need to grab that value and make that assignment for this poor fellow!" And js does some checking and discovers that the variable fool is in scope(i.e. you don't get an error for an undefined variable) because it was closed over by the onclick function. So js looks up the value of fool. The value of fool, like any variable, is the value that was last assigned to it.

    There is no inconsistency with 'this', so you shouldn't be confused about that either. For example, this line inside your onclick function:

    Code:
    var self=this;
    does nothing and can be deleted.


    Here are some simple examples to examine:

    1) A function closes over a variable--not a value:

    Code:
    function outer() {
      var x = 10;
    
      var func =  function () {
        console.log(x);
      }
    
      x = 11;
      
      return func;
    }
    
    returned_func = outer();
    
    //Time passes…
    
    returned_func();
    
    --output:--
    11
    a) When a function finishes executing, all local variables are destroyed.
    b) x is a local variable of outer(), and normally x would be destroyed after the call to outer() finished executing.
    c) However, because the anonymous function has x written inside it, x is not destroyed, and the anonymous function has access to x. In computer science speak, that state of affairs is indicated by saying, "the function closes over x".

    2) There is no difference here:

    Code:
    function outer() {
      var x = 10;
    
      var func =  function () {
        var fn = x;
        console.log(x);
      }
    
      x = 11;
      
      return func;
    }
    
    returned_func = outer();
    
    //Time passes…
    
    returned_func();
    
    --output:--
    11
    a) It's easy to get fooled when looking at the assignment var fn = x, and think, "I see x up there, and x is 10, so fn is being assigned 10. Wrong!

    b) The anonymous function has not executed yet, so no assignment takes place. The anonymous function does close over the variable x, which means that **when the function executes** it can access the variable x. And when the anonymous function executes, the value of x **at that time** will be assigned to fn.


    3) A twist:

    Code:
    function outer() {
      var x = 10;
      var y = x;
    
      var func =  function () {
        console.log(y);
      }
      
      x = 11;
    
      return func;
    }
    
    returned_func = outer();
    
    //Time passes …
    
    returned_func();
    
    --output:--
    10
    a) y is assigned the current value of x which is 10. After that, y is never assigned to again, so it's value remains 10.

    b) When the inner function executes, it prints out the current value of y, which is 10.

    There is a rule hiding in those examples: If a variable's value will change several times before you call the inner function, like x in the examples, you can capture the current value in a variable, e.g. y, and write y in the inner function instead of the changing variable x.

    --Note that the assignment takes place outside the inner function because the value you are interested in needs to be captured now--not when the inner function executes.

    --Note that the rule also applies to 'this'. 'this' is a constantly changing variable. Our code doesn't explicitly change 'this', though, instead js secretly and behind our backs changes the value of the variable named 'this'. So if at some point you have an inner function that needs to capture the current value of 'this', you can assign 'this' to a variable, e.g. y, and write y inside your inner function instead of 'this'. Conceptually, there is no difference between a changing variable like x in the examples and 'this'.


    4)

    Code:
    function outer() {
    
      func_arr = [];
    
      for(var i=10; i<13; ++i) {
    
        func_arr.push( function () {
          console.log(i);
        });
    
      }
    
      return func_arr
    }
    
    funcs = outer();
    
    for(var j=0; j<3; ++j) {
        funcs[j]();
    }
    
    --output:--
    13
    13
    13
    If the previous examples have made any sense, then hopefully the reason for this example's output is clear:

    1) Each inner function closes over the variable i.
    2) i increments to 13, which causes the for loop to terminate.
    2) Later, when the anonymous functions execute they display the current value of i, which is 13.


    5) Now, the trickiest part of closures in javascript. To remedy the problem in the previous example, let's use *the rule* and capture the current value of i in a variable named y, and then write y inside the inner functions instead of the changing variable i:

    Code:
    function outer() {
    
      func_arr = [];
    
      for(var i=10; i<13; ++i) {
    
        var y = i;
    
        func_arr.push( function () {
          console.log(y);
        });
    
      }
    
      return func_arr
    }
    
    funcs = outer();
    
    for(var j=0; j<3; ++j) {
        funcs[j]();
    }
    
    --output:--
    <not going to reveal it yet>
    Any sane person would analyze what happens in that code like this:

    a) Each time through the loop, a new variable y is declared.
    b) Therefore, each inner function closes over a different y variable, and each of their y's has a different value.
    c) So the output, should be 10, 11, 12.

    Wrong! The actual output is 12, 12, 12. Wtf?? That baffling output has to do with the way 'var' works. It turns out that this code:

    Code:
    function outer() {
      
      for(var i=10; i<13; ++i) {
        var y = i;

    is equivalent to:

    Code:
    function outer() {
      var y;
    
      for(var i=10; i<13; ++i) {
        y = i;
    In other words, despite where you use var to declare a variable inside a function, the variable is global to the entire function. To keep from confusing yourself, you should declare all variables at the top of a function.

    So because of the way var works, there is only one y variable, and all the inner functions close over the same variable; there is no way to get each of the inner functions to close over a different variable. Enter fretburner's solution. Each time you call a function, new local variables are created, which solves the problem with the var declaration inside the for-loop not creating a new variable every time. (If you've understood everything so far, then you should be able to identify a line in the function that doesn't do anything.)

  9. #9
    Gre aus'm Pott gold trophysilver trophybronze trophy
    Pullo's Avatar
    Join Date
    Jun 2007
    Location
    Germany
    Posts
    5,320
    Mentioned
    178 Post(s)
    Tagged
    9 Thread(s)
    Hi Ray,

    It is also worth noting that JavaScript 1.7 introduces the let keyword which can be used to establish variables that exist only within the context of a for loop.

    So, for example, this:

    Code JavaScript:
    <script type="application/javascript;version=1.7">
      function prepWidg(fix){
        var sel=new Array('.wDel', '.wUp','.wDwn', '.wAdd');
        var foo= new Array('delWidget', 'moveUp', 'moveDwn', 'newWidget');
     
        for (k=0,ll=sel.length; k<ll; k++){       
          bType=fix.querySelectorAll(sel[k]);
          let fool=foo[k];
          for (i=0,l=bType.length;i<l; i++ ){
            bType[i].onclick=function(e){  
              var fn=fool ; 
              var self=this; 
              master[fn](self); 
              return false; 
            }
          }
        }
        return fix;
      }
    </script>

    would work as originally intended, where all I have done is change var fool=foo[k]; into let fool=foo[k];.

    Unfortunately, this will only currently work in Firefox, so fretburner's solution is obviously to be preferred.

    If you're interested, you can read more here.

  10. #10
    SitePoint Wizard
    Join Date
    Mar 2001
    Posts
    3,537
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I was going to mention let, but I couldn't get it to work in any browser, nor with node.js using the --harmony flag:
    Code:
    function outer() {
    
      func_arr = [];
    
      for(var i=10; i<13; ++i) {
    
        let y = i;
    
        func_arr.push( function () {
          console.log(i);
        });
      }
    
      return func_arr
    }
    
    funcs = outer();
    
    for(var j=0; j<3; ++j) {
        funcs[j]();
    }
    Code:
    $ node --version
    v0.10.10
    
    $ node --harmony 1.js 
    
     for(var i=10; i<13; ++i) {
                               ^
    SyntaxError: Illegal let declaration outside extended mode

  11. #11
    Gre aus'm Pott gold trophysilver trophybronze trophy
    Pullo's Avatar
    Join Date
    Jun 2007
    Location
    Germany
    Posts
    5,320
    Mentioned
    178 Post(s)
    Tagged
    9 Thread(s)
    Your code works for me on FF, as long as I enclose it in:

    HTML Code:
    <script type="application/javascript;version=1.7"></script>

  12. #12
    SitePoint Wizard
    Join Date
    Mar 2001
    Posts
    3,537
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    In case anyone cares, here is how to run my last example(which had a mistake in it anyway) in node.js:

    Code:
    function outer() {
    
      func_arr = [];
    
      for(var i=10; i<13; ++i) {
    
        let y = i;
    
        func_arr.push( function () {
          console.log(y);   //<**********fixed mistake
        });
      }
    
      return func_arr
    }
    
    funcs = outer();
    
    for(var j=0; j<3; ++j) {
        funcs[j]();
    }
    Code:
    $ node --use-strict --harmony 1.js 
    10
    11
    12

  13. #13
    SitePoint Wizard
    Join Date
    Mar 2001
    Posts
    3,537
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Okay, thanks. That works for me in FF 21, and if anyone cares: not Safari 5.1.9.

  14. #14
    Gre aus'm Pott gold trophysilver trophybronze trophy
    Pullo's Avatar
    Join Date
    Jun 2007
    Location
    Germany
    Posts
    5,320
    Mentioned
    178 Post(s)
    Tagged
    9 Thread(s)
    Quote Originally Posted by 7stud View Post
    console.log(y); //<**********fixed mistake
    Yeah, I assumed that was a typo

    Quote Originally Posted by 7stud View Post
    That works for me in FF 21, and if anyone cares: not Safari 5.1.9.
    I thought that only Mozilla browsers support the let keyword and that IE, Safari, Chrome, etc don't.
    http://kangax.github.io/es5-compat-table/es6/

    Did you read anything different?

    N.B. Although Chrome claims to have let support with the experimental flag set, I couldn't get it to work. Nor it seems could other people.


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •