views:

492

answers:

3

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?

+1  A: 

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
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
+1  A: 

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
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
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
+7  A: 

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
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
Yes, I finally understood your question, and rewrote my answer. Hope this helps.
Marius
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
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
The reason this is the way it works is because of scopes. In JavaScript you only have function scopes.
Marius
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