Home > Mobile >  ReferenceError: can't access lexical declaration 'Foo' before initialization
ReferenceError: can't access lexical declaration 'Foo' before initialization

Time:05-02

Running the following code:

<!doctype html>
<html>
<head>
  <meta charset="UTF-8">
</head>
<body>
<script>
  main()
  class Foo { }
  function main() { Foo() }
</script>
</body>
</html>

generates ReferenceError: can't access lexical declaration 'Foo' before initialization (on Firefox 91.5.1esr, anyway).

A simple fix is to move the class definition up a line. Replacing class Foo with function Foo() will also eliminate the error message.

Question: Is there a reference to the standards, and/or a clear description, explaining this behavior?

CodePudding user response:

A class is a ClassDeclaration in the spec. This is classified as a DeclarationPart, which is classified as a StatementListItem for the purposes of LexicallyScopedDeclarations.

When a block or function is evaluated initially (before the code in the block actually starts running), it will do something like:

33. Let lexDeclarations be the LexicallyScopedDeclarations of code.
34. For each element d of lexDeclarations, do
  a. NOTE: A lexically declared name cannot be the same as a function/generator declaration, formal parameter, or a var name. Lexically declared names are only instantiated here but not initialized.
  b. For each element dn of the BoundNames of d, do
    i. If IsConstantDeclaration of d is true, then
      1. Perform ! lexEnv.CreateImmutableBinding(dn, true).
    ii. Else,
      1. Perform ! lexEnv.CreateMutableBinding(dn, false).

These LexicallyScopedDeclarations are identifiers created with ES6 syntax, and exclude var. (Identifiers created with var are classified as varNames, or VarDeclaredNames, which go through a different process than steps 33-34 above.)

So, at the beginning of a block or function, class identifiers (and const and let identifiers) all have bindings that have been created in the environment, but have not been initialized in the environment. Initialization occurs when BindingClassDeclarationEvaluation runs, which does:

5. Perform ? InitializeBoundName(className, value, env).

This only happens when the engine is actually running the code inside the block or function and comes across class SomeClassName. (This is separate from the process referenced earlier, which is when the engine is only looking through the text of the block to get a list of identifiers that are declared inside.)


When a block is running, and you try to instantiate something with new, EvaluateNew runs, which does, among other things:

Let constructor be ? GetValue(ref).

which eventually runs GetBindingValue, which has:

2. If the binding for N in envRec is an uninitialized binding, throw a ReferenceError exception.

The binding only gets initialized when InitializeBoundName runs - that is, when the engine comes across the class declaration.

CodePudding user response:

A simple way to understand why this error happens involves understanding hoisting. Before execution, an interpreter (like a browser engine) will move all variables and functions to the top of the file, likely for ease of access. But more modern instances of variables and functions, such as classes and function expressions, might get hoisted to the top but without being initialized. This is why that although the class is found, it has no proper value assigned to it, even if empty, thus an error occurs.

  • Related