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 化为 varNames
或 VarDeclaredNames
,其过程与上面的步骤 33-34 不同。)
因此,在块或函数的开头,class
标识符(以及 const
和 let
标识符)都具有 创建的绑定 在环境中,但尚未在环境中 初始化 。 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,但是没有给它赋值,即使是空的,也会出错。
运行以下代码:
<!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 化为 varNames
或 VarDeclaredNames
,其过程与上面的步骤 33-34 不同。)
因此,在块或函数的开头,class
标识符(以及 const
和 let
标识符)都具有 创建的绑定 在环境中,但尚未在环境中 初始化 。 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,但是没有给它赋值,即使是空的,也会出错。