How does the JS scope of these blocks work?

2019-06-24 15:41发布

问题:

Can anyone explain why the following produces 1,2 and the other produces 5? Should they not both produce 5?

//produces 1,2
(function () {

    var a = [5];

    function bar() {
        if (!a) {
          var a = [1, 2];
        }
        console.log(a.join());
    }

    bar();

})();

Based on reading some articles about JS closure, I expect them both to produce 5. Can't seem to find an article anywhere that would give some insight as to why the first block produces otherwise.

//produces 5
(function () {

    var a = [5];

    function bar() {
        if (a) {
          console.log(a.join());
        }
        else {
          console.log([1, 2].join())
        }
    }

    bar();

})();

Thanks!

回答1:

Due to javascripts var hoisting, this code:

(function () {
    var a = [5];
    function bar() {
        if (!a) {
          var a = [1, 2];
        }
        console.log(a.join());
    }
    bar();
})();

is equivalent to this code:

(function () {
    var a = [5];
    function bar() {
        var a; // a === undefined at this point
        if (!a) {
          a = [1, 2];
        }
        console.log(a.join());
    }
    bar();
})();

So you can see, a will indeed be falsey (i.e. !a === true) when the if condition is tested



回答2:

Avoid re-declaring var a = [1, 2]; and just initialize as a = [1, 2]; in your top function. Like @Jaromanda explained the variable is hoisted in JavaScript.



回答3:

Jeromanda X has given the right answer, it's due to var hoisting, I'd just like to add on to illustrate better.

Your 2 codes blocks are different in that you created a new variable in one and did not in the other. To be fair you should change code block #2 to the following

//produces [1,2] now
(function () {

  var a = [5];

  function bar() {
      if (a) {
        console.log(a.join());
      }
      else {
        var a = [1,2];
        console.log(a.join())
      }
  }

  bar();

})();

If you had omitted var in both code blocks you wouldn't have run into this scoping problem both code blocks would have produced 5



回答4:

We know that every function creates its own context and can have its own local variables and things.

We therefore presume that writing var a = [5] , in the context of top most function context, will make it available and accessible by the context of any new nested functions we may write.

And this is where we encounter a behavior which is most probably 'undocumented' as well as 'unexpected'.

The 1at example of the op

function bar(){
    if(!a) { 
           var a = [1,2];
          }
    console.log(a);
    }
>> 1,2

has it return a local value of 1,2. where we can see that the if conditional of the bar function context is treating the host function variable a, as undefined wherefore it is negating it !a to true.

Why is that? A commentator insists in hoisting. But what hoisting? There's absolutely no interference of a hoisting principle in a final result or behavior in the two versions of the same function at which the only difference is in conditional argument: one checks if 'a' is false and the other, if it's true.

Hoisting! What hoisting? If it were for "hoisting" - the !a operation should unmistakably return false because of the fact that a is already defined with a truthy value on a higher context accessible to any and every lower context functions of the host. But quite the contrary

As a consequence of if(!a [false] ) therefore true from conditional return, it is allowing the execution of a new declaration of a variable with the same name using the var key, as if it's out of reach or doesn't exist in a higher scope at all.

And it shouldn't. But it should.

Now trying the same thing without the negation of the if argument in conditional, we'll get an error and an assignment failure as in:

 function bar(){
    if(a) { 
           var a = [1,2];
          }
    console.log(a);
    }
>> undefined

[!same as when using if(!!a) ] We've changed nothing except that now we are asking if a is true so that we can create a new local 'a' variable with value [1,2];

What is going on?! The if conditional which is in explicit need for the a argument to be true in order to proceed is looking up and "climbing it down" to the local context of the function bar.

For some reason it is handled as an attempt to re-declare the variable of a host function and refusing to initialize and assign a value to new variable with the same name in a local context. That's a confusion because of the conditional which now refers to the host variable it is refusing to declare and assign a value to a local a.

However, on the console log a local variable a is being returned and it is undefined.

Which is unexpected; contradictory; defiant but seemingly correct! It is correct because the local a variable was initialized but didn't get a value correctly to begin with. We've tried to re-declare the one from the higher context, and it wasn't allowed.

The only good thing is - the behavior appears to be uniform across browsers.