1

Why, in this snippet, does trying to access bar not cause a Reference Error? I've read questions / answers that try to explain the difference between let and var, but none appear to explain the difference in behavior of the top and bottom examples.

var foo;
let bar;

if( foo ) ()=>{}; // I expected this to be fine
if( bar ) ()=>{}; // I expected this to throw a reference error

console.log('finished');

(Feel free to just answer that question, my life story and why I ask below)

I've been trying to break the bad habit of over-using var. While writing some new code, I came across an issue I hadn't seen before: Using if( myValue ) { ... } was throwing a Reference Error for myValue not being defined. "Well, duh, I'm trying to see if it is defined!"

I would declare myValue, and throughout the course of a function, I may assign it a value. Towards the end of the function, I check to see if it had a value, and if so, would perform some extra actions.

I remembered in my reading about let vs var that var will basically act as though you created the container for your value at the top of the scope, whereas let, I thought it would do so at the time you declare it, but seems to do so at the time you assign it. So, I could use var myValue;, let myValue = undefined, or if( typeof myValue !== 'undefined' ) { .. }. I get that the last one is the best bet to ensure this type of thing doesn't happen again, even if it is butt ugly.

Afterwards, I wrote the above little snippet to explain the bug to a co workers... and it worked?!? I'm trying to figure out what exactly was going on here that I may have missed, because I want to understand exactly what went wrong and learn from it. But I'm really scratching my head now, if I wasn't earlier, because I thought I not only figured out the problem, but fixed it... only to discover it may have been something else only tangentially related.

As a side note: My code did declare multiple variables with one let, using commas between them. Could this have been a factor? I did re-read those declarations several times and they are all formatted properly:

let value1 = 3,
    value2,
    value3 = 982,
    // ...
    myValue; // Add ` = undefined` to stop the Reference Error

// ...

if( myValue ) { /* ... */ } // Reference Error thrown here
Jake T.
  • 4,308
  • 2
  • 20
  • 48
  • Possible duplicate of [What's the difference between using "let" and "var" to declare a variable in JavaScript?](https://stackoverflow.com/questions/762011/whats-the-difference-between-using-let-and-var-to-declare-a-variable-in-jav) – Taplar Jul 20 '18 at 22:37
  • Without reading any text: uninitialized variables are initialized to `undefined`. In other words, `let x = undefined;` and `let x;` are identical (freak cases not included) – ASDFGerte Jul 20 '18 at 22:40
  • @ASDFGerte it seems I've run into some sort of freak case then. Is there anything in my bottom example that might point in the direction of one of these freak cases? The bottom is where adding `x = undefined` instead of just declaring x resolved a reference error issue, the example for the question at the top was a snippet I wrote to show what the original issue was, and didn't have the behavior I expected based on the solution to the issue. – Jake T. Jul 23 '18 at 14:37
  • The cases i thought about do not accidentally happen, and i don't know of any which would immediately explain your behavior. My best guess is there is a semicolon in your list: `let value1 = 3; myValue;`. The code would additionally be in non-strict mode, and you are accidentally operating on a global variable. – ASDFGerte Jul 23 '18 at 15:03
  • @ASDFGerte I had looked for that, too. The only potentially weird one I saw was a variable assigned to the results of a boolean expression, so I added some parenthesis around the boolean expression to ensure I didn't have an order of operations problem, but saw no difference. However, if this were the case, wouldn't the variable not throw a reference error, since it would now be a global var instead of let and default to undefined? – Jake T. Jul 23 '18 at 15:23
  • Can't you just post the code, or a shortened version that still produces the issue? Without that, we are just guessing around blindly, and the most likely case is you making some mistake in the code we can't guess. PS: assigning to the result of a boolean expression is an error. – ASDFGerte Jul 23 '18 at 15:28
  • @ASDFGerte Ahh, I found a semi colon in the middle of a line in my grouped `let`s while boiling my code down to post... You are right, it was an odd mistake in my code. Adding `= undefined` worked because it hoisted my variable to the global scope, whereas without it, no global variable was made. I misunderstood the `let` vs `var` declaration to be `let` variables not existing until they're assigned, but I see that declaring them sets them to undefined, as well. I just didn't. And I meant I assigned the results of an expression to the variable, not the variable to the boolean expression. – Jake T. Jul 23 '18 at 20:24
  • Looks like my glass ball was right, see above comment :) Note that this is an issue that `"use strict";` will avoid - this global assignment does not work there, and will give a proper error (as it should). It's kind of your own fault for writing non-strict code. – ASDFGerte Jul 23 '18 at 20:37
  • @ASDFGerte Is "use strict" disabled in VSCode or something, because I've tried setting it and find no apparent change in my code, when I know I have several things that violate strict mode going on. Is it possible something else could be disabling use strict? Everything I've read said I should just be able to put the line "use strict"; at the top of my file. – Jake T. Jul 25 '18 at 14:24
  • I'm also kind of curious, with the semicolon in my list, shouldn't that mean my last variable was treated as global, and therefore I would be able to reference it in my if statement? It should hoist up and find a global variable I thought? I found the "what" was wrong with my code, but I must still be missing a piece of the "why" – Jake T. Jul 25 '18 at 14:25
  • This IIFE e.g. will produce an error: `(_ => { "use strict"; myValue = 4; return myValue; })();`. On FF for example, this gives "ReferenceError: assignment to undeclared variable myValue", which is what i would expect. `"use strict";` must be the first statement of a file or function code. Everything declared in a strict-mode context will also be in strict mode, so writing it once at the top of the file should work. Note that all chances of strict-mode are at execution time only (use TypeScript or similar if you want compile-time errors, which i can also only suggest for bigger projects). – ASDFGerte Jul 25 '18 at 15:52
  • There are methods to get to non-strict, while being nested inside a strict context, but these afaik always involve `eval` or the `Function` constructor. An expression statement like `myValue;` is attempting to read `myValue`, but without prior assignment to it, even in non-strict, there will be no `myValue` (the error should be thrown at the botched declaration already). In non-strict, the assignment `myValue = 4;`, without prior declaration, will set the property `myValue` of the global object, which is imho very questionable behavior for a language. Without declaration, there is no hoisting. – ASDFGerte Jul 25 '18 at 16:01
  • In the JS spec, there are several imho questionable cases for non-strict mode. In this case, [PutValue](https://tc39.github.io/ecma262/#sec-putvalue) has the odd case for non-strict code, that it just uses the global object, when the reference cannot be resolved, see "step 5". – ASDFGerte Jul 25 '18 at 16:09
  • To give an example for the "no hoisting" part, consider this non-strict snippet, which will throw: `(_ => { myValue; myValue = 4; })();` and compare it to `(_ => { myValue; var myValue = 4; })();`, which works fine. In the latter example, the declaration will be hoisted, and `myValue` will be initialized to undefined at the start of the function. Also note that `(_ => { myValue; let myValue = 4; })();` would again be an error, because the expression-statement `myValue;` is attempting to read the variable inside the TDZ. Imho `let` behaves more intuitive than `var`. – ASDFGerte Jul 25 '18 at 16:31

2 Answers2

2

let bindings are created at the top of the (block) scope containing the declaration, commonly referred to as "hoisting". Unlike variables declared with var, which will start with the value undefined, let variables are not initialized until their definition is evaluated. Accessing the variable before the initialization results in a ReferenceError. The variable is in a "temporal dead zone" from the start of the block until the initialization is processed.

More info: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let

Schws
  • 91
  • 6
  • This is not what I'm experiencing, though. I expected a reference error, but did not get one. I thought this was the cause of an issue I had experienced, but when I wrote this quick snippet to test / show that was the cause, it worked without error. – Jake T. Jul 23 '18 at 14:34
  • Oh, now I understand. Your case seems a bit odd and my guess is there is something else going on in the rest of the code. So really, your question here is if there is a difference in behavior in declaring a single variable or a comma-separated list of variables. Demo: [link](https://jsfiddle.net/trschweers/6mujrw5g/) – Schws Jul 23 '18 at 19:19
  • You were correct, I found a misplaced semi colon amongst the other variables I was declaring / initializing with the single `let`. I'm still kind of confused as to how this variable didn't throw a reference error where I thought I declared it, as it was basically doing what my bottom example did with a semicolon instead of a comma for one of the first variables... all the variables with default values makes sense that they got hoisted, but the last one looks like it should have been a reference error because it was just calling an un-declared variable and doing nothing with it. – Jake T. Jul 23 '18 at 20:31
  • Still scratching my head, but sounds like my problem was solved! I was in a dead zone and was just under the impression I was not. – Jake T. Jul 23 '18 at 20:31
2

Unassigned does not mean uninitialised. The let bar; statement does the same as let bar = undefined; - it is initialised with undefined as the default value unless you provide an explicit initialiser ("right hand side") in the variable declaration.

Variables declared with let will throw a reference error when you access them before the initialisation, when their declaration statement is evaluated:

let bar; // = undefined;

if (bar) {} // this is fine

console.log('finished');

if (bar) {} // this throws a reference error

let bar; // = undefined;

console.log('finished');

See this topic about the temporal dead zone for more details.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375