ReferenceError: can't access lexical declaration 'Foo' before initialization

ReferenceError: can't access lexical declaration 'Foo' before initialization

运行以下代码:

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

生成 ReferenceError: can't access lexical declaration 'Foo' before initialization(无论如何在 Firefox 91.5.1esr 上)。

一个简单的修复方法是将 class 定义向上移动一行。将 class Foo 替换为 function Foo() 也会消除错误消息。将 Foo() 更改为 new Foo() 没有 帮助:错误仍然存​​在。

问题:是否有参考标准,and/or清晰的描述,解释了这种行为?

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

当一个块或函数被初始评估时(在块中的代码实际开始之前 运行),它将执行 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).

这些LexicallyScopedDeclarations是用ES6+语法创建的标识符,不包括var。 (使用 var 创建的标识符被 class 化为 varNamesVarDeclaredNames,其过程与上面的步骤 33-34 不同。)

因此,在块或函数的开头,class 标识符(以及 constlet 标识符)都具有 创建的绑定 在环境中,但尚未在环境中 初始化 BindingClassDeclarationEvaluation 运行时会发生初始化,它会:

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

只有当引擎实际上是 运行 块或函数中的代码并遇到 class SomeClassName 时才会发生这种情况。 (这与前面提到的过程是分开的,后者是引擎仅查看块的文本以获取内部声明的标识符列表的时候。)


当一个块是 运行 时,您尝试用 new 实例化某些东西,EvaluateNew 运行,除其他外:

Let constructor be ? GetValue(ref).

最终运行 GetBindingValue,它有:

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

绑定仅在 InitializeBoundName 运行时初始化 - 也就是说,当引擎遇到 class 声明时。

理解为什么会发生此错误的一种简单方法涉及理解提升。在执行之前,解释器(如浏览器引擎)会将所有变量和函数移动到文件的顶部,可能是为了便于访问。但是更现代的变量和函数实例,例如 classes 和函数表达式,可能会被提升到顶部但没有被初始化。这就是为什么虽然找到了class,但是没有给它赋值,即使是空的,也会出错。