javascript

Why does the temporal dead zone exist?


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

Can anyone explain to 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);


Solution

  • In some languages, a variable comes into existence the moment its declaration is encountered, but in JavaScript, a variable comes into existence the moment execution enters its scope: the surrounding function for var variables and arguments, the surrounding block for let and const variables.

    Consider the following code, which is intentionally poorly written:

    var color = 'blue';
    
    function hedgehog (superSonic) {
      color = 'black';
      if (superSonic) {
        var color = 'gold';
      }
      console.log(color);
    }
    
    hedgehog(false);    // black
    console.log(color); // blue
    

    You might expect the first line of the hedgehog function to set the outer variable to 'black', but because there's a var declaration for a color variable within the function, the outer variable is actually left unchanged. All var declarations inside a function are taken into account the moment the function is called. So even though the line var color = 'gold'; is never executed, the inner variable still exists (with the initial value undefined) and the first line of the function assigns to it.

    It's best to place declarations for variables before you use them to avoid confusion, but JavaScript didn't originally enforce this with its var variables. It even allowed the same variable to be declared in more than one place. One benefit of these relaxed rules is illustrated here:

    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;
          }
        }
      } else {
        var count = 0;
        for (var i = 0; i < mobs.length; ++i) {
          if (mobs[i] instanceof NiceMob) {
              mobs[i].despawn();
              ++count;
          }
        }
      }
      console.log(`Removed ${count} out of ${i} mobs.`);
    }
    

    Here, i and count are declared twice in the same function, but JavaScript won't complain. There is only one variable with each name, and we can log them even outside of the conditional block.

    Block-scoped variables created with let and const were added much later to the language. They behave similarly to var in that they shadow outer scope variables the moment their scope is entered, but unlike var variables they are considered unusable before their declaration is encountered, and declaring them twice is a syntax error (which can be caught when parsing code, long before its execution).

    Trying to access a let or const variable prior to its declaration throws an error, which prevents mistakenly using an inner scope variable accidentally.

    let color = 'blue';
    function hedgehog (superSonic) {
      if (superSonic) {
        color = 'gold'; // ReferenceError if executed!
      }
      let color = 'black';
      return color;
    }
    

    Finally, typeof someundefinedvar evaluates to "undefined" when given an identifier that doesn't correspond to an existing variable. This lets you check if a global variable was declared by an external script. If a block-scope variable with that name is declared, the special behavior of typeof doesn't trigger, and therefore it tries to check the type of the value in that variable, and throws a ReferenceError if the variable is still in TDZ.

    Here's an example of code where this could help us catch a bug:

    let jQuery;
    if ((typeof $) !== "undefined") {
      jQuery = $;
    }
    var $ = s => document.querySelectorAll(s);
    

    In this code, the var $ declaration at the bottom shadows any global variable $ that might have previously existed, so typeof $ will always return "undefined" and the block will never execute. If we had used let instead of var, then a ReferenceError would be thrown by typeof $ and we would notice our mistake.