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.