6

I know what is the temporal dead zone (TDZ), but I can't understand the purpose of it existence.

Can anyone explain me why it was created?

What is the logic of it raising a ReferenceError or a SyntaxError instead of returning undefined?

ReferenceError:

console.log(typeof foo);
let foo;

SyntaxError:

let foo;
let foo;

Undefined:

console.log(typeof foo);
Community
  • 1
  • 1
user7393973
  • 2,270
  • 1
  • 20
  • 58
  • Undefined can have multiple causes. Being a reference- or a syntax error tells you more about what's wrong. – Shilly Mar 10 '17 at 12:32
  • @Shilly, if that was the reason then multiple other situations that cause undefined would cause ReferenceError or SyntaxError instead just because it gives more information. There must be more to it. – user7393973 Mar 10 '17 at 12:36
  • Isn't that a big assumption? People have been working on the 'let' spec for years. Vanilla JS was written in 10 days? Having it return undefined makes less sense to me. The current 'let' handling is more in line with other programming languages, so having javascript no longer be one of the standouts with special variable rules might be valuable. I'll include some links, maybe one of them has more arguments. https://rainsoft.io/variables-lifecycle-and-why-let-is-not-hoisted/ and https://davidwalsh.name/for-and-against-let – Shilly Mar 10 '17 at 12:43
  • 2
    [Why is there a “temporal dead zone” in ES6?](http://www.2ality.com/2015/10/why-tdz.html) – Andreas Mar 10 '17 at 12:43
  • You should also look up "variable hoisting". It's a big JS thing, they used to use it in interview questions to try and stump you. "let" removes the implied unpredictability (which is fine if you're used to it, otherwise it can be problematic). – Tim Consolazio Mar 10 '17 at 13:33
  • @Andreas, that is almost the only place that talks about this subject but it's a little vague, hard to understand and is missing information in my opinion. – user7393973 Mar 10 '17 at 13:39

4 Answers4

3

Using a variable before initialising it is always a mistake. Making this an error is reasonable as it helps the developer to notice their mistake and be able to fix it. The behaviour of var starting as undefined caused too many problems already, and they would have gotten worse if similar semantics were used for const variables or with static type annotations.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • But if that's the actual answer to this question then why didn't they just made it cause error to both `let` and `var`? I can't find any deep/official explanation to this. – user7393973 Mar 10 '17 at 13:41
  • 3
    @user7393973 They surely would have loved to make it an error for `var` as well, but Javascript has to stay backwards-compatible to avoid breaking the web. Remember ES5 strict mode that introduced reference errors on undeclared variables? Still not happening in sloppy mode. – Bergi Mar 10 '17 at 13:45
  • 1
    That can't be it because if the issue was that, they would simply deprecate it and remove it in the future just like they usually do, @Bergi. Like the [`for each...in`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for_each...in) for example. – user7393973 Mar 10 '17 at 13:50
  • @user7393973 No, they cannot remove it. Deprecating it (similar to `__proto__`) might have been possible but not worth it; and there are still many reasonable uses for `var` e.g. in the global scope (TDZ is not their only difference). Regarding `for each`, that was never a standard (given ES4 failed) but always just an experimental Mozilla extension. – Bergi Mar 10 '17 at 13:54
  • What about all of [this](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Deprecated_and_obsolete_features) then? – user7393973 Mar 10 '17 at 14:00
  • 1
    Yes, basically all of these have been ES3 bugs, Mozilla-specific syntax features (that got refined and now are a bit different in ES6), or stuff that turned out to encourage bad practises. `var` is none of those. – Bergi Mar 10 '17 at 14:08
  • I guess I get it now that initializing variables is part of [best practices](https://www.w3schools.com/js/js_best_practices.asp). Is "assigning a value to a variable" the only way to initialize it or is there other ways? – user7393973 Mar 10 '17 at 14:49
  • `let` and `const` variables are initialised in the statement where they are declared, although for `let` the initialiser ("assignment") is optional. Other kinds of variables (parameters, functions) are declared and initialised in many different ways. – Bergi Mar 10 '17 at 15:09
  • I'm sitting here racking my brain for a reason you'd ever intentionally use a variable before it is initialized, and the only things I can come up with that are remotely plausible could also be solved by a pre-defined boolean and it would use less keystrokes with a `!` rather than using `typeof` check. So the only reason is if you need practice typing and will delete the code when you're done punching it in. – Chris Baker Jun 09 '17 at 03:07
2

It makes sense for a variable to exist from the moment it is defined. Due to the way variables from an outside scope are accessible within nested scopes, the following code is very confusing:

var foo = 'out';

function bar () {
  foo = 'in';
  console.log(foo);
  var foo;
}

bar();

What does the function bar() do? It creates a new variable called foo and assigns 'in' to it before showing it in the console. The variable from the outside scope is still equal to 'out' after that. So it's a much smarter thing to define variables before you use them. I believe the fact that you can use a variable before it is declared is only a question of implementation simplicity and efficiency, but I'm boldly guessing here.

However, in JS, variables created with the var keyword are accessible from within their function, not their block. This allows more permissive syntax like the following:

function setGameMode (mode) {
  if (mode === 'peaceful') {
    var count = 0;
    for (var i = 0; i < mobs.length; ++i) {
      if (mobs[i] instanceOf EvilMob) {
        mobs[i].despawn();
        ++count;
      }
    }
    console.log('Removed ' + count+ ' evil mobs out of ' + i);
    mobSpawner.evil = false;

  } else if (mode ==='chaotic') {
    var count = 0;
    for (var i = 0; i < mobs.length; ++i) {
      if (mobs[i] instanceOf NiceMob) {
          mobs[i].despawn();
          ++count;
      }
    }
    console.log('Removed ' + count + ' nice mobs out of ' + i);
    mobSpawner.nice = false;
  }
}

The i variable still exists after the for loop thanks to function-scoped variables. This is also why the var keyword allows you to define a variable twice. It just wouldn't be practical if you were forced to write var only once. With a let variable, this "loose" feature becomes useless as these variables should be freed as soon as they are no longer needed.

When you call a function in most programming languages, the execution environment creates a space in memory for the variables you'll need. Here, there's no way to tell if the variable i will be needed without actually running the code because it's inside an if block. Still, since JS has function-scoped variables, it has to create the variable from the start of the function. There's no need for this in the case of let variables.

As for the reason behind typeof someundefinedvar returning 'undefined', well it's because you need a way to check for variables that might have been declared in outside scopes. However, you don't need that feature for variables that are block-scoped. let variables are meant to be used immediately and thrown away.

Domino
  • 6,314
  • 1
  • 32
  • 58
  • I'm still reading your answer but that `var foo;` inside the function is causing that weirdness. But I don't think it should behave that way, it looks like a bug to me. – user7393973 Mar 10 '17 at 14:19
  • Yeah, but it's how JS works. `var foo` creates the local variable foo within the function, which shadows the outer foo. You're probably best to avoid such code, it's horrible ^^ – Domino Mar 10 '17 at 14:31
  • Good explanation, confusing topic. I'm not sure about anything now. I don't get, in the first code, why it happens what it happens. My experience tells me the following: `var foo = 'out';` creates a global (at least to the rest of the code) variable. Then inside the function, `foo = 'in';` changes its value. `var foo;` should just be declaring a local (function scoped) variable. – user7393973 Mar 10 '17 at 14:31
  • `var` and `let` statements run before anything inside the function. It's called hoisting. – Domino Mar 10 '17 at 14:32
  • So you mean it's like declaring `var foo;` in the beginning of the function and then the code `foo = 'in';` changes that? Why would they make it that way? That makes it impossible to use both variables (global and local) that have the same name while inside the function. – user7393973 Mar 10 '17 at 14:36
  • I understand now that hoisting moves declarations to the top. For example: `console.log(foo);` causes ReferenceError saying that foo isn't defined and `console.log(foo);var foo;` returns undefined. Now my question is, why does JS does that? That completely removes the point of declaring variables later in the code! – user7393973 Mar 10 '17 at 14:42
  • Damn, I never learned nothing like this before. This is killing my brain right now. I also took a look at `sloppy mode` and [`strict mode`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode). Anyway, thanks! I need brain food. :) – user7393973 Mar 10 '17 at 14:56
  • I searched a bit and I found that to solve the issue with the global and local variables inside the function can (maybe) be solved by using `window.varName` to refer to the global variable. [Source](http://stackoverflow.com/a/17363578/7393973). – user7393973 Mar 10 '17 at 15:20
  • 1
    Nice example code, nitpicking: the i variable would equal the count of all mobs - 1 at the end of the loop. If only nice or evil mobs are despawned, possibly a non-100 percent of the total, it wouldn't be nice to tell the log that # of all mobs - 1 were despawned no matter what happened. :) The example still conveys the concept you're explaining, I know it's a pedantic point. – Chris Baker Jun 09 '17 at 03:00
  • @ChrisBaker You're right, the code actually didn't do what the output implied. This should have fixed it :) – Domino Jun 09 '17 at 04:42
0
  1. console.log(typeof a); let a;

In above statement you didn't defined variable a and you are using it. In java script, it is necessary to defined a variable. that variable is a reference of your value So that's why is showing you an undefined reference error.

  1. let a; let a;

In 2nd statement you have defined a variable two time. js don't allow you to defined any variable more then one time into same block scope.

  1. console.log(typeof foo);

    In 3rd statement, As it is showing an error in first function that your variable is not defined. BY default it is undefined. and you can use a undefined variable.

Sudhanshu Jain
  • 494
  • 3
  • 11
  • 2
    You're missing the question, which is "why is it different for let and var". Case 1 and 2 would work with `var` but not with `let`. You didn't explain why case 3 is different from case 1. – Domino Mar 10 '17 at 13:34
  • Exactly what @JacqueGoupil just said. – user7393973 Mar 10 '17 at 13:36
  • Difference between var and let is "scope". Let has a limited scope and var is global. You can use it anywhere. – Sudhanshu Jain Mar 10 '17 at 17:04
  • No, he's asking why they throw different errors. I think the long and short of it is backward compatibility. It's possible, probable, that a future future version of EMCA script will BC break and start throwing a consistent error since the use of an undefined variable is progamatically the same error whether it's let or var. – Chris Baker Jun 09 '17 at 03:12
0

If you examine the documentation for those exceptions you linked in your question, you can see the why, which I don't think has been addressed here.

A side note first: it bears mentioning that exceptions didn't exist in EMCAScript until edition 3. var obviously predates this language feature by a lot. why var and let are different is largely due to the fact that exceptions were available when let was introduced, and not when var was introduced. That history underlines all of this. 1999 was a busy year, also!

Anyway, into the gears. Look what happens here:

// example A
(function () {
console.log(somethingUndefined - 1);
var somethingUndefined;
console.log('another operation');
})();

An error, then the code plows on. Can we catch this and deal with it?

// example B
(function () {
try {
    console.log(somethingUndefined - 1);
    var somethingUndefined = 50;
    console.log('another operation');
} catch (e) {
    console.log('I want to deal with problems here');
    return;
}

console.log('plowing on');
})();

Nope.

Behind the scenes, this code looks like this:

// example C
(function () {
var somethingUndefined = undefined;
try {
    console.log(somethingUndefined - 1);
    somethingUndefined = 50;
    console.log('another operation');
} catch (e) {
    console.log('I want to deal with problems here');
    return;
}

console.log('plowing on');
})();

There is no "temporal dead zone" because there is no point in example B & C where the variable somethingUndefined is not something. It's a typeof "undefined", somethingUndefined === undefined, that's not nothing. Then it's 50, but too late to be useful. Useful or not, we can use it to do things because it has a value. Line 1, line 8, it always has some value. Compare the difference in output here:

// example D
(function () {
try {
    console.log(somethingUndeclared - 1);
    console.log('another operation');
} catch (e) {
    console.log('I want to deal with problems here');
    console.log(e);
}
})();

At every point in example D, somethingUndeclared is nothing. It's an always dead zone. I am not sure which EMCAScript edition the above code began to throw exceptions, but it's less breaking for it to do so because this situation is always going to be an error.

Unless in some other part of the same scope OR the parent scopes somethingUndeclared was defined, probably for some other purpose. Or maybe the code var somethingUndeclared; is laying around somewhere in the scope or the parent scopes for some reason, so there's no exception thrown in anoy of the code above. That's the argument for using let or const almost exclusively.

When you use let or const, there is a "dead zone," a point in time when it isn't anything, then it will be something. It isn't undefined, it's an exception waiting to happen. Until the let statement is reached, it acts like an undeclared variable, throwing an exception, then at the declaration line it acts like a var with a value. It is a blending of the behavior of var with the outcome of an undeclared variable.

// example D
(function () { // temporal dead zone, something is nothing
try { // temporal dead zone, something is nothing
    console.log(something - 1); // exceptional behavior! temporal dead zone
    let something  = 50; // temporal live zone begins here!
    console.log('another operation');
} catch (e) {
    console.log('I want to deal with problems here'); // you could call this a dead zone
    console.log(e); // dead
}
// dead
})(); // way dead

Which moves us on to the begged question, "why not pull off the bandaid, deal with the BC break for the sake of consistency in the language?" The answer to that is because good enough is usually good enough. There are millions of lines of javascript managing accordions and image galleries and other UI bells and whistles. One opts-in to a more mature set of language features when EMCAScript is filling mission-critical roles. Or you just don't like sloppy code for your sweet tab navigation. Consistency in the language is not worth breaking all of the code out there that is good enough to do what it needs to do most of the time. Imagine the mountains of good enough that have accumulated since it was even possible to throw an exception in this language ('99).

When we need it to do what it is supposed to do all of the time, we can harness these features. When it would be a catastrophe for undefined = 100; to be in some file in the project, we can opt-in to exceptions. When the progressive enhancement fails and we get no tooltips, we have a less-nice UX than we'd hoped for. var and let have different histories and different weight on their shoulders, so they're likely to always act differently.

Chris Baker
  • 49,926
  • 12
  • 96
  • 115