4

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!

Harvester316
  • 381
  • 1
  • 8
  • 17
  • You can avoid problems like this, and stop worrying about hoisting, by just declaring your variables at the top of the function, as best practices dictate. –  Nov 22 '16 at 05:49
  • @torazaburo I heard of this but could u provide a source? – jkris Nov 22 '16 at 05:53
  • @jkris You could start here: http://javascript.crockford.com/code.html#variable%20declarations –  Nov 22 '16 at 06:43
  • @torazaburo Crockford, nice! thanks! – jkris Nov 22 '16 at 07:10

4 Answers4

5

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

Jaromanda X
  • 53,868
  • 5
  • 73
  • 87
  • Thanks for the point in the right direction. I read some other docs and got the idea now. Need only understand the following statement and the confusion goes away. "Hoisting is the mechanism of moving the variables and functions declaration to the top of the function scope." https://rainsoft.io/javascript-hoisting-in-details/ – Harvester316 Nov 22 '16 at 08:07
  • hoisting has absolutely nothing to do with the problem. – Bekim Bacaj Nov 22 '16 at 08:39
  • 2
    what is your damage @BekimBacaj? – Jaromanda X Nov 22 '16 at 10:14
1

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.

dinesh
  • 342
  • 2
  • 13
  • Thanks. Yes, I know that should be avoided. I just didn't know this was the very reason why and the terminology behind it. :) – Harvester316 Nov 22 '16 at 08:15
1

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

jkris
  • 5,851
  • 1
  • 22
  • 30
-2

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.

Bekim Bacaj
  • 5,707
  • 2
  • 24
  • 26
  • *allow the execution of the following declarations* No, declarations are not executed sequentially; they are hoisted, as other answers have made clear. –  Nov 22 '16 at 05:50
  • yes they are, the if condition tests if variable "a" is defined e.g. true and because var a is already true the conditional allows for the local `var a = [1, 2];` to be executed. – Bekim Bacaj Nov 22 '16 at 05:54
  • I don't think were talking about the same issue here – Bekim Bacaj Nov 22 '16 at 05:55
  • I think I'll have to rewrite my answer because the op is asking something something else but wrote two blocks of code that don't correspond in logic. – Bekim Bacaj Nov 22 '16 at 06:00
  • @JaromandaX - I just tested the 1st examaple in my FF console and I've encountered an error - which is completely unexpected - test this: `(function () { var a = [5]; function bar() { if (a) { var a = [1, 2]; } console.log(a.join()); } bar(); })();` What do you think about that? – Bekim Bacaj Nov 22 '16 at 06:11
  • @JaromandaX, see if I managed to explain it in my revision of the previous answer. Thanks – Bekim Bacaj Nov 22 '16 at 07:29
  • @JaromandaX there's no `join()` at my emphasis example explanation at all. – Bekim Bacaj Nov 22 '16 at 07:41
  • And no @JaromandaX I understood "hoisting" when it didn't even have a name :) – Bekim Bacaj Nov 22 '16 at 07:42