1

I hit a subtle bug which has been distilled as follows:

function huh() {
    for (var i = 0; i < 10; i++) {
        if ( i % 2 == 1 ) {
            var x = i;
            console.log(i + ' ' + x);
        }
        else {
            console.log(i + ' ' + x);
        }
    }
}

huh();

Firstly, I would challenge even experienced JavaScript programmers to correctly predict the exact output of this program on paper only. But mainly, JavaScript seems to be mixing dynamic and lexical scoping. And it seems there is no block scope, only function scope, which is basically blowing away my whole concept of scoping in JavaScript. Can someone explain in reference to the standard, and maybe a bit of rationale? This seems highly counter-intuitive.

BaseZen
  • 8,650
  • 3
  • 35
  • 47
  • `0 0 | 1 0 | 2 2 | 3 2 | 4 4 | 5 4 | 6 6 | 7 6 | 8 8 | 9 8` – Mouser Feb 25 '15 at 21:56
  • 1
    There is no block scoping except for `let` in ES6. Do some research on "hoisting," and consider also that `x` is undeclared the first time through this loop. – Marc Feb 25 '15 at 21:58
  • Nope ran it and I mixed up the odds and evens, needs to be vice versa. – Mouser Feb 25 '15 at 21:58
  • 1
    I'd recommend you read through this chapter of 'Eloquent Javascript', a great read that goes into detail on some of the questions you have. http://eloquentjavascript.net/03_functions.html – Zach Sadler Feb 25 '15 at 21:59
  • Since `var x` hoists, the first log will be `0 undefined` and the second 1 1. – dfsq Feb 25 '15 at 22:00
  • *Feels validated* Eloquent JavaScript on this exact point: "If you find this odd, you’re not alone." – BaseZen Feb 25 '15 at 22:04

2 Answers2

1

There is block scoping in JavaScript from ecmascript version 6 with the let keyword.

That aside, the way it works with the var statement is that the statement floats to the top of the function before the function is being executed. Variable declarations take place before anything else, regardless of the blocks they are in, including inside for loop statements. The actual assignments of the variables does happen in place, so they are undefined but not undeclared until the line of code where they get assigned something.

As for your specific example, remember that primitive values like numbers are not assigned by reference but by value, and that the updating of x will only happen on uneven numbers and will be undefined the first time round, but if it would be objects, they would refer to the same object once you assigned one to the other (regardless of if/else conditions).

To provide further confusion, note that console in browsers is asynchronous, so you could print objects to the console (not primitives though) and inspect them where you would not get the state of the object at the time of the console.log statement, but at a later time. That's very confusing for debugging.

Yes JavaScript in practice definitely has a bit of a learning curve.

  • Thanks for all the details. Removed syntax error (transcription error). – BaseZen Feb 25 '15 at 22:34
  • Most of this answer really seem to aim for confusion? I don't see what ["objects are references"](http://stackoverflow.com/a/5314911/1048572) and ["console is async"](http://stackoverflow.com/q/23392111/1048572) have to do with the question. Apart from that, even strict mode is fine with `var` redeclarations. – Bergi Feb 25 '15 at 22:39
  • The behavior of this piece of code is seriously different when the variables are primitives or objects, so I explained that since that's not necessarily obvious to beginning javascript developers. I might be mistaking about var redeclarations, so I'll check that. If it's ok in strict mode, than it's jshint that complains about it anyways. –  Feb 25 '15 at 22:43
  • seems it was just jshint, so I removed the part about double declaration –  Feb 25 '15 at 23:29
  • Huh? How would that code be different with objects - the variable declarations work the same way? Not sure what you mean. – Bergi Feb 25 '15 at 23:49
  • In the loop, the x is being assigned based on a condition. If both x and i where objects, they would always be the same, the condition wouldn't change that. Secondly because of the console being asynchronous. When looking at it, to inspect values at different times, in a browser, will not show the object state at the time of the log command... Have a look at [this](http://funkyimg.com/i/UztL.png). I tried to explain that all in my answer, but I'm sorry if it's worded in a confusing way. –  Feb 26 '15 at 00:03
  • Ah, you meant "object properties instead of variables", not "objects instead of primitive values". – Bergi Feb 26 '15 at 01:10
1

This somewhat puzzling effect is a combination of function scope and variable hoisting.

You should consider the above code equivalent to:

function huh() {
  var i, x;
  for (i = 0; i < 10; i++) {
    if ( i % 2 == 1 ) {
      x = i;
      console.log(i + ' ' + x);
    }
    else {
      console.log(i + ' ' + x); }
  }
} 
Valentin Waeselynck
  • 5,950
  • 26
  • 43