```for(;;){ var .. }``` is misleading


  • 🚽 Regular

    Continuing the discussion from Please reply to this post.:

    @Maciejasjmj said:

    ```
    for (i = 0; i < Discourse.MessageBus.callbacks.length; i++)
    {
    var j = i; //probably makes no sense, but whatever
    var f = Discourse.MessageBus.callbacks[j].func; //same
    Discourse.MessageBus.callbacks[j].func = (function() {
    var k = j;
    var f2 = f;
    return function(t) { console.log("Callback " + k + " fired!"); f2(t); }
    })();
    }

    
    Friggin' closures.</blockquote>
    I think it bears saying: writing code like this &mdash; where there is a `var` statement inside a  block that isn't a complete function body &mdash; is misleading.
    
    ```js
    function outputSquares()
    {
    
        for(i = 1; i <= 10; i++)
        {
          var ii = i * i;  // Please don't do this
    
          setTimeout( function(){ console.log(ii); }, 1000 );
        }
    
    }
    
    // Will output "100" 10 times
    

    It suggests there is a new variable ii being defined every time through the body of the for, which is then accessed inside the anonymous function. This is false (the first part, that is).

    Due to variable hoisting, every time you declare a variable in JavaScript with the var keyword†, that variable will be scoped to the inner-most function body containing that statement (or to the global scope). Not the inner-most block.

    † There is a let keyword which lets you declare variables with block scope, but it is unfortunately not very well supported.


    Variable hoisting means the code above is equivalent to this:
    function outputSquares()
    {
        var ii;  // Variable declaration hoisted up here. Its initial value is undefined
    
        for(i = 1; i <= 10; i++)
        {
          ii = i * i;  // Assign a value to ii
    
          setTimeout( function(){ console.log(ii); }, 1000 );  // Sets up an anonymous function for running later
        }
    
        // Value of ii is 100 by the time we exit the loop.
        // The variable isn't declared outside this function body, but the value is there somewhere in memory
    }
    
    // Some time after, ten anonymous functions run.
    // Each outputs the current value of ii: 100
    

    So what *should* you do?

    You should create scopes by creating named or anonymous functions, or passing values as arguments to pre-existing functions:

    // Alternative 1: ugly but functional
    
    function outputSquares()
    {
    
        for(i = 1; i <= 10; i++)
        {
          (function(){  // Create a new scope by using an anonymous function
    
            var ii = i * i;  // Won't be hoisted above here
    
            setTimeout( function(){ console.log(ii); }, 1000 );
    
          })();  // End of scope. Run the anonymous function immediately
        }
    
    }
    outputSquares();
    
    // Alternative 2: use a proper function.
    // It can be locally scoped because JS supports nested functions
    
    function outputSquares()
    {
      
        for(i = 1; i <= 10; i++)
        {
            helper(i);
        }
    
        // The loop body is now a proper inner function.
        // Function declarations are hoisted too, which is why we can call helper above
        function helper(i){
          var ii = i * i;
          setTimeout( function(){ console.log(ii); }, 1000 );
        };
    
    }
    outputSquares();
    
    // Alternative 3: almost like alternative 2, but ii is now an argument you pass along
    function outputSquares()
    {
      var ii;  // This will be available 
      
      for(i = 1; i <= 10; i++)
      {
        ii = i * i;
        helper(ii);
      }
    
      // Remember: ii has the value 100
      
      function helper(ii){
        // But the references to ii inside helper() refer to the argument, not the outer variable,
        // and its value is whatever it was at call time.
        // This is true also for the scope of the anonymous function passed to setTimeout
        setTimeout( function(){ console.log(ii); }, 1000 );
      };
    
    }
    outputSquares();
    

    I hope this is helpful to someone.

    Also, I'm taking the chance of seeing what happens if I use ```...``` in the title. It'll probably appear like regular backticks, I expect, but whatever



  • http://i.imgur.com/yjRPHik.png

    Somehow you broke something when I clicked on a notification from my post. It might have been me screwing around in the console, though.


  • kills Dumbledore

    I got bitten by this when first delving into Javascript. Without prior knowledge, the similarity in syntax to C based languages makes it really unintuitive.

    For the project I was working on I ended up using a C# wrapper to call Javascript snippets so looping could be controlled in a language I understood.


  • 🚽 Regular

    There is a third alternative I forgot: explicitly binding.

    function outputSquares()
    {
      var ii;
      
      for(i = 1; i <= 10; i++)
      {
        ii = i * i;
        setTimeout( (function(){ console.log(this); }).bind(ii), 1000 );
      }
    
    }
    outputSquares();
    


  • This topic was automatically closed after 61 minutes. New replies are no longer allowed.


  • Discourse touched me in a no-no place


  • Discourse touched me in a no-no place



  • Good summary, +1.

    A prerequisite knowledge for writing anything remotely ambitious in javascript.



  • Aww, I didn't read the title too closely and thought the topic was about ShellShock - maybe even a Discourse ShellShock vulnerability being discovered!


  • BINNED

    tl;dr: Javascript sucks.

    I wondered why there were so many languages that compile to JavaScript. This goes a long way toward explaining it.


  • Discourse touched me in a no-no place

    It was always the screwiness with this that drove me up the wall. The scoping of var was at least easier to understand; it has its own logic.



  • As I'm reading it, the problem, such as i is, is not with the scoping of j&f, which, as is stated, are hoist to the innermost surrounding function, but the use of j & f as free variables in the callback function, and then again free in the function it returns, via k & f2.



  • @tufty said:

    As I'm reading it, the problem, such as i is, is not with the scoping of j&f, which, as is stated, are hoist to the innermost surrounding function, but the use of j & f as free variables in the callback function, and then again free in the function it returns, via k & f2.

    The problem is that I banged the code in three minutes with heavy reliance on trial and error. And now we have the WTF Commitee all over it.



  • @dkf said:

    It was always the screwiness with this that drove me up the wall. The scoping of var was at least easier to understand; it has its own logic.

    My solution to the "this" problem: don't use it. Ever.

    Problem solved.


  • :belt_onion:

    Good information to know! It seems like I've run into this before, but if I have, it wasn't recent.
    I likely did something like the "proper function" method above.

    But man, it wouldn't be TDWTF if we didn't throw some serious WTF solutions at this shit! And it's not TDicsourseWTF without race conditions. So I present to you the following solution...
    [code]
    function outputSquares()
    {
    var ii = [];
    for(i = 1; i <= 10; i++)
    {
    ii.push( i * i ); // Please don't do this
    setTimeout( function(){ console.log(ii.shift()); }, 1000 );
    }
    }
    [/code]

    Suck our balls, race conditions.

    A fiddle for you http://jsfiddle.net/y9b1br31/ -making it even more wtf-worthy, now it appends string dom via jquery just to get on some nerves!


  • :belt_onion:

    To be honest, since multi-threading in Javascript requires Web Workers, there is almost certainly no race condition involved with the above, only because of the interval specified as being the same every time.

    If the spec called for random intervals for the timeouts, then the arrayshift method would not work the same because it will ALWAYS print in ascending order (the for-loop finishes before the first timeout function is called because this javascript is single-threaded) rather than getting them mixed together. see: http://jsfiddle.net/xkpLLc5n/ vs http://jsfiddle.net/prLwvxb7/1/



  • @cartman82 said:

    My solution to the "this" problem: don't use it. Ever.

    Problem solved.

    I have actually been known to write: var that = this; to deal with scoping issues in the past.


  • Discourse touched me in a no-no place

    @Arantor said:

    var that = this;

    Whenever I see that, I think of…

    https://www.youtube.com/watch?v=95IvJFhNZME

    One of my favourite games of all time.



  • @darkmatter said:

    A fiddle for you http://jsfiddle.net/y9b1br31/ -making it even more wtf-worthy, now it appends string dom via jquery just to get on some nerves!

    A few more touches and you have yourself a nice randomize array function.

    @Arantor said:

    I have actually been known to write: var that = this; to deal with scoping issues in the past.

    That's basic level fuckery.

    You know you're into some deep javascript shit where every constructor starts with

    function MyObject() {
        var thisMyObject = this;
        // and never use this again
    }
    


  • My two rules of JavaScript:

    1. Use JSLint. Its rules seem arbitrary and draconian at first, but you eventually realize that 60% of JavaScript is crap and JSLint (or an equivalent) is one of the few ways to avoid accidentally stepping in the crap. JSLint would have pointed out the bad var statement in the OP.
    2. "this" is defined by whoever called you. There is no language definition for what "this" will be, which is very different from most other languages that have a "this". BTW, you set "this" my doing:
    somefunction.call(whateverYouWantToBeThis, args...)
    




  • @Jaime said:

    1. Use JSLint.

    I tried. Then I saw this:

    http://i.imgur.com/C7jTZeC.png

    and suddenly realized I'm unable to progress.



  • I understand your pain. Just know that it's asking an ideal and it really understands that the code can't possibly be that good, but tries to inspire you to feel good about it - if you know you need JSLint, you are automatically better than 99.74% of the JS-writing community, give or take a decimal place.



  • @Arantor said:

    you are automatically better than 99.74% of the JS-writing community, give or take a decimal place.

    997.4% ?



  • Something like that, yes. JavaScript's overloading of + to mean both addition and concatenation does occasionally mean such joys happen.


  • Discourse touched me in a no-no place

    @Arantor said:

    I have actually been known to write: var that = this; to deal with scoping issues in the past.

    In Progress, database record-scoping rules can be slightly arcane: in particular, it's possible for a buffer's scope to bleed into a procedure. One of the (controversial) ways people deal with this is defining an alternate buffer, and giving it the same name. So you'll see

    define buffer SomeBuffer for SomeBuffer.

    This guarantees that inside the procedure, any reference to SomeBuffer refers to the local one, not any other ones that may happen to be in scope at the time.


  • :belt_onion:

    @Jaime said:

    somefunction.call(whateverYouWantToBeThis, args...)

    because i'm an asshole! (cue song)

    [code]
    var Thing = function(){
    this.ii=[];
    }
    Thing.prototype = {
    outputSquares:function()
    {
    for(i = 1; i <= 10; i++)
    {
    this.ii.push( function() { return i * i; } );
    setTimeout(this.output.bind(this),1000)
    }
    },
    output:function(){
    console.log(this.ii.shift()());
    }
    }
    var that = new Thing();
    that.outputSquares();
    [/code]

    Props to the people that know what that will print without testing it first.


  • 🚽 Regular

    @antiquarian said:

    I wondered why there were so many languages that compile to JavaScript.
    It's the assembler of the browser operating system.

    @Arantor said:

    I have actually been known to write: var that = this; to deal with scoping issues in the past.
    It's a pretty standard thing to do. I prefer to use self or a more descriptive name though.

    @Jaime said:

    My two rules of JavaScript:
     3. Use "use strict";


Log in to reply