Home > OS >  Why does `const arg = arg;` produce a "Cannot access before initialization" error?
Why does `const arg = arg;` produce a "Cannot access before initialization" error?

Time:12-07

I found really weird behavior that I never thought about before. I'm not sure if this is related to TDZ because I thought TDZ was about from outer scope to inner scope, not the other way around like this case. Pay attention to arg in below examples.

// Works

const test = {
   func: (arg) => {
      const obj = {
         foo: arg,
      }
      return obj.foo;
   }
}
// Error

const test = arg => {
   {
      const arg = arg; // Cannot access 'arg' before initialization
   }
}

CodePudding user response:

The problem is that you've declared a new arg variable inside the function block, and that hides the arg variable in the outer scope, e.g. the one declared as a function parameter.

Thus the arg on the right hand side of the const arg = arg; assignment references the same variable as the one referenced on the left hand side. It is NOT referencing the arrow function arg parameter. Thus you are doing exactly as the error says, referencing a variable before you initialized it (while trying to initialize it!).

This is easily demonstrated by using unique names:

const test = arg => {
   {
      const inner_arg = arg;
   }
}

Why would you use the same name anyway? Not only does it result in the above problem, it is impossible to read code. Perhaps you are doing this out of habit like you would in a class constructor? But in that case you can differentiate between the parameter and the class field with this, e.g. this.arg = arg.

CodePudding user response:

The reason for the error message is that let and const declarations are both block-scoped, which means that they are only accessible within the { } surrounding them. So because of const or let the variable (arg) from the outer scope won't be accessed if another variable (arg) inside a block scope gets defined.

Or in other words: The variable arg inside a parentheses or curly brackets is not the same as the arg you pass to the function because you make use of let or const inside.

While parsing the block scope the engine already reserves the name for EVERY variable defined inside. But they will only be accessible after the declaration and evaluation using const or let.

So reading it, while writing to it, causes the error you see.

var variable;
{ // [block/env start] 
   let variable = variable; // ReferenceError: Cannot access 'variable' before initialization
} // [block/env end]

What happens during let variable = variable is that it has to read the right hand side before it assigns the value/reference to the left hand side, but per definition the variable is not available before declaration, therefor it throws the error.

Another example would be:

var variable;
{
  console.log(variable); // ReferenceError: Cannot access 'variable' before initialization
  let variable;
}

The execution order is similar to the assignment in your example and throws the same error. It won't access the outer variable because another variable gets defined inside that block scope using let/const.

You can also take a look of the Let and Const Declarations.

let and const declarations define variables that are scoped to the running execution context's LexicalEnvironment. The variables are created when their containing Environment Record is instantiated but may not be accessed in any way until the variable's LexicalBinding is evaluated. A variable defined by a LexicalBinding with an Initializer is assigned the value of its Initializer's AssignmentExpression when the LexicalBinding is evaluated, not when the variable is created. If a LexicalBinding in a let declaration does not have an Initializer the variable is assigned the value undefined when the LexicalBinding is evaluated.

  • Related