3

Would someone help explain why the two snippets of code below print out different results?

The difference is inside the conditional statement. In the first, there is a local variable assignment of 'Jack' to name, and the conditional is truthy (meaning !name evaluates to true). In the second, the same name 'Jack' is assigned but to the global variable name, but the conditional is falsy (!name is false). My question is, if all else is the same, why would the first conditional be true and the second is false if what is changed is inside the conditional body?

My only explanation is that the body of the conditional is read by the JS interpreter first, which is how it determines whether name is global/local, whether the variable declaration needs to be hoisted or not, and finally, the different name values logged.

Shouldn't the conditional boolean be evaluated first before even starting to interpret its body? Then in both cases, variable 'name' would be evaluated as 'undefined'... Any help would be greatly appreciated!

There are a few really good resources about hoisting/scope contexts but I'm not finding one that specifically answers this question.

http://javascriptissexy.com/javascript-variable-scope-and-hoisting-explained/

var name = "Paul"; 
function users () {
    if (!name) {
        var name = "Jack";  //<== difference is here, *inside* conditional body
    }
    console.log(name);
}
users(); //outputs "Jack"

vs.

var name = "Paul"; 
function users () {
    if (!name) {
       name = "Jack";  //<== difference is here, *inside* conditional body
    }
    console.log(name);
}
users();  //outputs "Paul"
Christine
  • 93
  • 1
  • 8
  • 1
    In one case there is a local variable, in the other there is not. Why would the outcome be the same? – Bergi May 17 '16 at 23:13
  • @Bergi The return values would be the same if the hoisted variable declaration didn't change the logic of the conditional. – Jonathan Gray May 17 '16 at 23:13
  • *"shouldn't the variable 'name' be evaluated as 'undefined' in both cases"* Uh no? In the second example, `name` is not a local variable. It refers to the global variable which has the value `"Paul"`. Hoisting only applies to variable declarations. There is no variable declaration inside the function in the second example, – Felix Kling May 17 '16 at 23:24
  • @Felix King: Yes I get the difference between global and local variables in this case. I get that putting the `var` in front of a variable creates a local variable. I get that declaring `name = 'Jeff'` would change the global variable instead. The part I'm actually confused about is why these inner statements are even being evaluated in the first place BEFORE the conditional `if (!name)` is even evaluated. Maybe it'll help if I ask a more directed question. Are variables declared INSIDE a conditional statement (whether it's true or false) hoisted to the top? – Christine May 18 '16 at 01:56
  • That question seems to differ from the one in your post (where you wonder why *both* are not `undefined`). Anyways, yes, `var` declarations are scoped to the function. – Felix Kling May 18 '16 at 02:03
  • @Felix, no, I clarified my question but it never changed - it seems you read that sentence without the context around it. Thanks. – Christine May 18 '16 at 02:07
  • *"Shouldn't the conditional boolean be evaluated first before even starting to interpret its body? Then in both cases, variable 'name' would be evaluated as 'undefined'"* seems to indicate that you understand why `name` is `undefined` when you have `var name` (first example), but wonder why it isn't the second case. But I don't understand why you would think the variable should be `undefined` in the second example? But yes, before a function body is executed, the runtime looks for all variable and function declarations and seeds the scope. That's the definition of hoisting. – Felix Kling May 18 '16 at 02:28
  • @FelixKling: Excellent, my confusion was around the conditional, but it's clear as crystal now :) Thank you! – Christine May 18 '16 at 03:53
  • Bonus: Try `let name = "Jack";` instead of `var name = "Jack";` in your first example to examine the distinction between `var` and `let` –  May 18 '16 at 13:20

3 Answers3

5

Variable declarations hoist to the top of the execution context, in this case the function users. Rewriting these to show how it looks from the hoisted perspective often clears up any confusion

var name = "Paul"; 
function users () {
    var name;//<- hoisted variable declaration
    if (!name) {
        name = "Jack";
    }
    console.log(name);
}
users(); //outputs "Jack"

vs.

var name = "Paul"; 
function users () {
    if (!name) {//no hoisted variable declaration, uses global
       name = "Jack";
    }
    console.log(name);
}
users();  //outputs "Paul"

Execution contexts contain several key components, the most relevant here are the lexical environment and variable environment. I cover the differences between the two (and some brief history) in more depth if you are interested here: https://stackoverflow.com/a/32573386/1026459

Community
  • 1
  • 1
Travis J
  • 81,153
  • 41
  • 202
  • 273
  • and what about `var name = "Paul"; function users () { name = "Dietrich"; if (!name) { var name = "Jack"; } console.log(name); } users(); //outputs "Dietrich"` what just happened? :) – Bekim Bacaj May 17 '16 at 23:42
  • @BekimBacaj - Name was overwritten with "Dietrich", the check for !name was false and didn't enter, and then "Dietrich" was logged. In addition, now the global variable name has been changed to "Dietrich". – Travis J May 18 '16 at 00:00
  • Yes of course, but this doesn't explain why? The: `name="Dietrich"` is still global! – Bekim Bacaj May 18 '16 at 00:07
  • @BekimBacaj - It was never local in your tangential example. – Travis J May 18 '16 at 01:00
  • Thanks for the breakdown of steps, Travis. I understand hoisting and that variable declarations hoist to the top of the execution context. My original question was why `var name` is hoisted to the top in the first example and not the second. The only explanation I can think of is that the JS interpreter reads and evaluates the body of the conditional in each scenario `var name = "Jack"` and `name = "Jack"`, respectively, **before** the conditional is evaluated to be true/false, which is how it determines the need to hoist in the first place. That is what still confuses me. – Christine May 18 '16 at 01:17
  • @Christine - In the second example, there is no use of **var** and as a result there is no variable declaration, thus no hoisting. – Travis J May 18 '16 at 01:30
  • @TravisJ - correct. But that means the body text *inside* the conditional is read and evaluated *before* the conditional statement is evaluated. Otherwise, when (!name) was being evaluated, how did it take into account whether the variable was local or not? I always thought the conditional is evaluated first (*before* any reference to its body). by the same logic, it seems this should be true (but isn't): ```var name = 'christine' function test() { if(name === 'travis') { name = 'travis'; console.log('passes test') } } test() // does not print 'passes test' ``` – Christine May 18 '16 at 01:41
  • @TravisJ "now the global variable name has been changed to "Dietrich". " - But it's not! Global variable name remains completely untouched and is not affected at all - that's local variable being returned from the function. And that's why you need to read my article. – Bekim Bacaj May 18 '16 at 02:03
2

When you use the var , you are instantiating a variable in the current scope. - which in the first case, is the user function's scope.

When you don't use var, you simply don't have that variable in that scope (function). And since you already instantiated the variable name outside of the current scope (globally), you get that as the variable name

var name = "Paul"; 
function users () {
// var name;  is essentially hoisted to here - the top of the current scope
    if (!name) {
        (var) name = "Jack"; // that hoisted var is set here
    }
    console.log(name);
}
users(); //outputs "Jack"

other case:

var name = "Paul"; 
function users () {
    if (!name) {
       name = "Jack"; // name is hoisted outside of the scope, 
                      // but it already is declared as "Paul"
    }
    console.log(name);
}
users();  //outputs "Paul"
omarjmh
  • 13,632
  • 6
  • 34
  • 42
  • 1
    `var name = "Jack"` doesn't get hoisted, just the variable declaration does. It's the same as `var name;` or `var name = undefined`. – Jonathan Gray May 17 '16 at 23:18
0

It's the order!

The order of precedence. In document code during the parse time Functions get evaluated before the code begins executing. But declared variables have a greater precedence in any given context. And Functions are allowed to run only after all variables has been instantiated.

This is why the function with a declared variable "name" is returning the value of its local name. Because it's there already and the function doesn't have to 'look up' for its value in the outer scope.


EDIT

For deeper understanding, a more interesting example of the same case is here:

var name = "Paul";

   function users () {
          name = "Dietrich";
      if (!name) {
          var name = "Jack";
      }
      console.log(name);
   }

users(); // outputs "Dietrich"
console.log(name);  // again outputs "Paul"

So what just happened?

Isn't the declaration name = "Dietrich" supposed to be targeting the global 'name' variable value?

Why doesn't this same function persist in returning "Jack" as it did before? Or -Why is the output suddenly not "Jack" and of course not "Paul" anymore but strange and completely unexpected "Dietrich"?!

-It's for the very same reason it persisted retuning "Jack" instead of what following the function semantics and its conditional suggests to be expected, and that is "Paul".

It's for the reasons explained above. At the first sight and line of function declaration we have name = "Dietrich" clearly targeting the global "Paul" Than, we have the additional precaution, a conditional which should prevent the execution of "Jack" since there is already a variable "name" available from the outer scope. But to no avail...

Whereas to make matters even more confusing - the global "Paul" is still intact!

Wee are assigning "Dietrich" to 'name' variable from the outer scope only from our reading point. Because var(s) get evaluated before functions and long before the function body declarations start to execute.

And since if(condition){ doesn't create a scope of its own } it doesn't matter how deep in that function body do we declare the 'name' variable. Every equation has been resolved by now.

name = "Dietrich" will NOT modify the global 'name' of the outer scope anymore, because 'name' variable is already present in this (local) function scope, so normally "Dietrich" is overwriting the local "Jack" not the hitchhiker "Paul" of the Universe. And that's because var name has been already defined somewhere along the current scope.

So it's the same reason it used to return the 'unexpected' "Jack" before. And that's all.

Bekim Bacaj
  • 5,707
  • 2
  • 24
  • 26
  • 1
    who is the illiterate and extremely dumb retard that down voted the only correct answer on the thread - just wondering :) – Bekim Bacaj May 30 '16 at 20:59