6

I’ve run into a very strange (to me) problem with the var keyword. I’ve reduced it to a fairly minimal test case, and found it’s exhibited in Node.js (thus, V8 and Chrome), Safari 4’s inspector (thus, Nitro), and FireBug (obviously, SpiderMonkey). I was originally preparing a bug report, but since it’s so widely displayed, I’m going to assume that I completely misunderstand how JavaScript is supposed to scope and look up variables.

The test case is very small, and on GitHub here: http://gist.github.com/260067. The only difference between the first and second example is the inclusion of the var keyword.

Here, as well, is a similar test case that exhibits the same ‘problem’ in a different way: https://gist.github.com/698b977ee0de2f0ee54a

Edit: To preclude any more answers attempting to explain how cascading scope works, I’m intimately familiar with that. My problem, is that I don’t understand why the following code ‘works’ (in that it alert()s ‘outer,’ followed by ‘inner,’ and then again ‘outer’):

(function(){
  var foo = 'outer';
  alert("Outer `foo`: " + foo);

  (function(){
    foo = 'inner';
    alert("Inner `foo`: " + foo);

    var foo;
  })();

  alert("Outer `foo`: " + foo);
})();

The var foo; occurs in a completely irrelevant position to the re‐assignment of foo; so why does it affect that assignment in a very substantial way?

ELLIOTTCABLE
  • 17,185
  • 12
  • 62
  • 78
  • Why are you using an eval? There is absolutely no reason to use eval in the code you have posted. – Marius Dec 19 '09 at 13:38
  • To demonstrate the problem. The actual implementation is very different; you can see it in the wild here: http://github.com/elliottcable/poopy.js/blob/new-acquire/lib/from.js#L193 – ELLIOTTCABLE Dec 19 '09 at 13:39
  • Your last example works because 1) foo = 'inner'; assigns the value to the parent scope foo, which this function has access to. 2) you don't need to use var to declare a variable. – Marius Dec 19 '09 at 14:04
  • See, that’s wrong. That’s how I *thought* it would work, but it doesn’t. Run that code (I just tested it in V8 and Nitro/WebKit), the laster alert will still be ‘outer.’ That is, the `foo = 'inner';` is scoped into the sub‐scope, due to the `var foo;` at the end of the function. – ELLIOTTCABLE Dec 19 '09 at 14:10
  • ok, I get it now. I agree that it is confusing, but that is why one of the tips to using JavaScript is to declare all variables at the beginning of the function. It stops bugs like those you have come across today from happening. – Marius Dec 19 '09 at 14:14

3 Answers3

14

The thing is that unlike other languages, JavaScript creates all variables at the start of a function. This means that the code:

(function(){
    if(myVar == undefined){
        alert(myVar);
    }
    if(myVar == undefined){
        var myVar = 5;
    }
})();

Is actually compiled and interpreted as

(function(){
    var myVar;
    if(myVar == undefined){
        alert(myVar);
    }
    if(myVar == undefined){
        myVar = 5;
    }
})();

To create a variable and only have it available inside an if or loop block, you have to use let, which is a new JavaScript feature. I'm not sure how many browsers implement it yet (Firefox 3.5 does if you use <script type="text/javascript;version=1.7">).

(function(){
    if(myVar == undefined){
        alert(myVar);
    }
    if(myVar == undefined){
        let myVar = 5;
    }
})();
Marius
  • 57,995
  • 32
  • 132
  • 151
  • Yes, I’m aware of this, see my response to Amnon above. My confusion stems from the fact that it affects calls from *before it is defined in the local scope*. – ELLIOTTCABLE Dec 19 '09 at 13:47
  • Yes, I finally understood your question, and rewrote my answer. Hope this helps. – Marius Dec 19 '09 at 13:55
  • Yes, that helped quite a bit. See my clarification in the original question. I frankly still cannot understand why this happens the way it does; I suppose my intuitive grasp of JavaScript, the language, is not in line with the specification/implementation. Regardless, I’m going to mark this answer as ‘accepted.’ /= – ELLIOTTCABLE Dec 19 '09 at 13:57
  • What the hell is that `;version=` crap? Is that a proprietary Mozilla extension? I was *not* aware that MIME type data could include a version specification… – ELLIOTTCABLE Dec 19 '09 at 14:03
  • The reason this is the way it works is because of scopes. In JavaScript you only have function scopes. – Marius Dec 19 '09 at 14:05
  • 5
    elliottcable: `;version=` is _not_ part of the MIME type. It's data about the content. For example, look at this HTTP header: `Content-Type: text/plain; charset=UTF-8`. `; charset=UTF-8` is not part of the MIME type, it's just extra data that is used for parsing the content. – Eli Grey Dec 19 '09 at 14:49
2

var exports doesn't work exactly like local variables in many languages. It declares exports as local variable in the whole function instead of just the enclosing block (even though it appears after the first usage), so the function argument with the same name is hidden.

Edit: the let keyword works more conventionally (it declares a variable only for the containing block) but it isn't available in all versions of JavaScript.

Amnon
  • 7,652
  • 2
  • 26
  • 34
  • So the interpreter reads ahead, and notices the declaration of the variable… and the definition of the variable in the locals scope (attached to `undefined`), overrides that of the argument? That is *extremely* counter‐intuitive. How come the interpreter reads ahead in this instance? o.O – ELLIOTTCABLE Dec 19 '09 at 13:38
  • 1
    Yes it's counter-intuitive, but that's how it is... Regarding how it's done, the function is parsed before it is run. – Amnon Dec 19 '09 at 13:50
  • """How come the interpreter reads ahead in this instance?""" -- it looks ahead in EVERY instance. It's not a "line by line" parse/interpreter like some other languages, the parser reads ALL of the scripts before it starts executing anything. – Hejazzman Apr 13 '13 at 01:26
1

the inclusion of var means that the assignment of the {} is done to a local variable exports instead of the global variable exports, which means it has no effect.

fforw
  • 5,391
  • 1
  • 18
  • 17
  • Yes. That I understand. I *do* know basic JavaScript. My problem is that that comes *after* the call that we’re looking into; it should have no effect on whether or not `exports` is undefined at the time of the `alert()` call. Regardless of what comes afterwards, `exports` is *not* undefined at that point. – ELLIOTTCABLE Dec 19 '09 at 13:32