因为它们被明确设计为不允许这样做,因为这通常是一个编程错误。
let 和 const 被提升,但它只是被提升的绑定声明。 (粗略地说,“绑定”的意思是“变量”[或常量或参数...具有我们用来保存值的名称的事物]。)绑定直到稍后才初始化,当let 或const 语句在代码的逐步执行中到达。您不能使用未初始化的绑定(以任何方式),这就是您收到错误的原因。
相比之下,var 声明 和 的初始化都被提升了; var 绑定使用值undefined 进行初始化。如果var (var a = 42) 上有初始化值,稍后在代码的逐步执行中到达var 语句时,该部分被视为简单赋值(a = 42)。使用let 和const,它不仅仅是简单的赋值,而是绑定的初始化,允许使用它。
这是一个具体的例子,说明let 如何提升声明而不是初始化,以及为什么它有助于防止编程错误:
let a = 1;
function foo() {
a = 2; // <=== Which `a` should be assigned to?
console.log(a);
// code
// code
// code
// code
// code
// code
// code
// code
let a = 3;
console.log(a);
}
foo();
在该代码中,foo 顶部的赋值似乎应该赋值给外部 a,因为(据我们所知,自上而下阅读)范围内没有其他 a。但是有,因为foo 底部的let 被吊起。由于内部 a 未初始化,因此执行分配时出错。
相比之下,var 没有错误,但很容易混淆 a 分配在 foo 的顶部。
在 cmets 中,您表示您仍然不理解声明绑定但未初始化绑定的含义。我认为“初始化”¹的两个(轻微)含义在这里让你感到困惑(当我进入这些东西时它们让我感到困惑),所以让我们稍微改变一下术语。
绑定有一个与之关联的标志,表明它们是否可以使用。我们称它为usable 标志:usable = true 表示可以使用绑定,usable = false 表示不能使用。使用该术语,上面的示例是这样处理的:
-
当创建脚本的执行上下文时:
- 为其中的所有顶级声明创建绑定:
-
let a = 1; 的let a 部分创建了一个名为a 的绑定,其usable 标志设置为false(尚不能使用)。
- 函数声明 (
function foo() { }) 创建一个名为 foo 的绑定,其 usable 标志设置为 true(可以使用),其值设置为 undefined。
- 通过创建它们定义的函数并将它们分配给绑定来处理上下文中的函数声明。所以
foo 得到了它的函数值。
-
当在代码的逐步执行过程中遇到let a = 1;语句时,它会做两件事:将usable标志设置为true(可以使用)并设置@的值987654363@转1。
-
当调用foo 并创建调用的执行上下文时,会创建顶级声明的绑定:
- 名为
a 的绑定由let a = 3; 创建,其usable 标志设置为false(尚不能使用)。
-
当在代码的逐步执行中到达a = 2;语句时,a解析为内部a绑定(foo中的那个,由let a = 3;声明),但是该绑定的usable 标志是false,因此尝试使用它会引发错误。
-
如果我们没有a = 2; 语句,所以没有抛出错误,那么当逐步代码执行到达let a = 3;声明,它会做两件事:将usable标志设置为true(可以使用)并将a的值设置为3。
这里是 foo 更新了一些 cmets:
function foo() {
// The local `a` is created but marked `usable` = `false`
a = 2; // <=== Throws error because `a`'s `usable` is `false`
console.log(a);
let a = 3; // <=== If there weren't an error above, this would set
// `usable` to `true` and the value of `a` to `3`
console.log(a);
}
¹ “我认为“初始化”¹的两个(稍微)含义在这里让您感到困惑......”我指的是两个含义:
- “初始化”绑定(使其可用,将
usable 设置为true),并单独进行
- “初始化”就像设置绑定的初始值一样。
它们是不同的东西。