let
let
声明声明可重新赋值的、块级作用域的局部变量,可以选择性地将每个变量初始化为一个值。
尝试一下
语法
let name1;
let name1 = value1;
let name1 = value1, name2 = value2;
let name1, name2 = value2;
let name1 = value1, name2, /* …, */ nameN = valueN;
参数
描述
用 let
声明的变量的作用域是以下最靠近 let
声明的某个大括号封闭的语法
- 块 语句
switch
语句try...catch
语句- 如果
let
位于语句的头部,则为 某个for
语句 的主体 - 函数主体
- 静态初始化块
或者,如果以上都不适用
- 对于在模块模式下运行的代码,则为当前 模块
- 对于在脚本模式下运行的代码,则为全局作用域。
与 var
相比,let
声明有以下区别
let
声明的作用域是块级作用域,以及函数级作用域。let
声明只能在到达声明的位置后才能访问(见 暂时性死区)。因此,let
声明通常被认为是 未提升 的。let
声明不会在脚本顶层声明时在globalThis
上创建属性。let
声明不能在相同作用域中被其他任何声明 重新声明。let
开始 声明,而不是语句。这意味着不能使用单独的let
声明作为块的主体(这很有道理,因为没有办法访问该变量)。jsif (true) let a = 1; // SyntaxError: Lexical declaration cannot appear in a single-statement context
请注意,let
在 非严格模式 中用 var
或 function
声明时是允许作为标识符名称使用的,但应避免使用 let
作为标识符名称,以防止出现意外的语法歧义。
许多样式指南(包括 MDN 的指南)建议在变量在作用域中没有被重新赋值时使用 const
而不是 let
。这明确表明变量的类型(或原始类型的值)永远不会改变。其他人可能更喜欢使用 let
来表示被修改的非原始类型。
在 let
关键字后的列表称为绑定列表,并由逗号分隔,其中逗号不是 逗号运算符,=
符号不是 赋值运算符。后面变量的初始化器可以引用列表中前面的变量。
暂时性死区 (TDZ)
用 let
、const
或 class
声明的变量从块的开头到代码执行到达声明和初始化该变量的位置,被认为处于“暂时性死区”(TDZ) 中。
在 TDZ 内,该变量尚未初始化为某个值,任何访问该变量的尝试都会导致 ReferenceError
。当执行到达代码中声明该变量的位置时,该变量将被初始化为某个值。如果在变量声明中没有指定初始值,则它将被初始化为 undefined
值。
这与 var
变量不同,如果在声明之前访问 var
变量,则它将返回 undefined
值。以下代码演示了在声明之前访问 let
和 var
时结果的不同。
{
// TDZ starts at beginning of scope
console.log(bar); // "undefined"
console.log(foo); // ReferenceError: Cannot access 'foo' before initialization
var bar = 1;
let foo = 2; // End of TDZ (for foo)
}
术语“暂时性”之所以使用,是因为该区域取决于执行顺序(时间),而不是代码书写顺序(位置)。例如,以下代码有效,因为即使使用 let
变量的函数出现在变量声明之前,但函数是在 TDZ 外部调用的。
{
// TDZ starts at beginning of scope
const func = () => console.log(letVar); // OK
// Within the TDZ letVar access throws `ReferenceError`
let letVar = 3; // End of TDZ (for letVar)
func(); // Called outside TDZ!
}
对 TDZ 中的变量使用 typeof
运算符将抛出 ReferenceError
{
typeof i; // ReferenceError: Cannot access 'i' before initialization
let i = 10;
}
这与对未声明的变量和值为 undefined
的变量使用 typeof
不同。
console.log(typeof undeclaredVariable); // "undefined"
注意: let
和 const
声明仅在当前脚本被处理时才会被处理。如果您在一个 HTML 中有两个在脚本模式下运行的 <script>
元素,则第一个脚本不受第二个脚本中声明的顶层 let
或 const
变量的 TDZ 限制,但如果您在第一个脚本中声明了 let
或 const
变量,则在第二个脚本中再次声明它会导致 重新声明错误。
重新声明
let
声明不能与其他任何声明位于相同的作用域中,包括 let
、const
、class
、function
、var
和 import
声明。
{
let foo;
let foo; // SyntaxError: Identifier 'foo' has already been declared
}
函数主体内的 let
声明不能与参数同名。catch
块内的 let
声明不能与 catch
绑定的标识符同名。
function foo(a) {
let a = 1; // SyntaxError: Identifier 'a' has already been declared
}
try {
} catch (e) {
let e; // SyntaxError: Identifier 'e' has already been declared
}
如果您在 REPL 中进行试验,例如 Firefox 网页控制台(工具 > 网页开发者 > 网页控制台),并在两个单独的输入中运行两个同名的 let
声明,您可能会收到相同的重新声明错误。有关此问题的进一步讨论,请参阅 Firefox 错误 1580891。Chrome 控制台允许在不同的 REPL 输入之间进行 let
重新声明。
您可能在switch
语句中遇到错误,因为只有一个代码块。
let x = 1;
switch (x) {
case 0:
let foo;
break;
case 1:
let foo; // SyntaxError: Identifier 'foo' has already been declared
break;
}
为避免错误,请将每个 case
包裹在一个新的代码块语句中。
let x = 1;
switch (x) {
case 0: {
let foo;
break;
}
case 1: {
let foo;
break;
}
}
示例
作用域规则
由 let
声明的变量在其声明的代码块以及任何包含的子代码块中都有作用域。这样,let
的工作方式与 var
非常相似。主要区别在于 var
变量的作用域是整个封闭函数。
function varTest() {
var x = 1;
{
var x = 2; // same variable!
console.log(x); // 2
}
console.log(x); // 2
}
function letTest() {
let x = 1;
{
let x = 2; // different variable
console.log(x); // 2
}
console.log(x); // 1
}
在程序和函数的顶层,let
与 var
不同,不会在全局对象上创建属性。例如
var x = "global";
let y = "global";
console.log(this.x); // "global"
console.log(this.y); // undefined
TDZ 与词法作用域结合
以下代码在显示的行导致 ReferenceError
。
function test() {
var foo = 33;
if (foo) {
let foo = foo + 55; // ReferenceError
}
}
test();
if
代码块被评估,因为外部 var foo
有一个值。但是,由于词法作用域,此值在代码块内部不可用:if
代码块内部的标识符 foo
是 let foo
。表达式 foo + 55
抛出 ReferenceError
,因为 let foo
的初始化尚未完成——它仍然处于时间死区。
这种现象在以下情况下可能会令人困惑。指令 let n of n.a
已经在 for...of
循环的代码块的作用域内。因此,标识符 n.a
解析为指令本身(let n
)第一部分中 n
对象的属性 a
。这仍然处于时间死区,因为其声明语句尚未到达并终止。
function go(n) {
// n here is defined!
console.log(n); // { a: [1, 2, 3] }
for (let n of n.a) {
// ^ ReferenceError
console.log(n);
}
}
go({ a: [1, 2, 3] });
其他情况
在代码块内使用时,let
将变量的作用域限制为该代码块。注意 var
与 let
之间的区别,var
的作用域在它被声明的函数内。
var a = 1;
var b = 2;
{
var a = 11; // the scope is global
let b = 22; // the scope is inside the block
console.log(a); // 11
console.log(b); // 22
}
console.log(a); // 11
console.log(b); // 2
但是,以下 var
和 let
声明组合是一个SyntaxError
,因为 var
不是块级作用域,导致它们处于相同的作用域。这会导致变量的隐式重新声明。
let x = 1;
{
var x = 2; // SyntaxError for re-declaration
}
使用解构声明
每个 =
的左侧也可以是一个绑定模式。这允许一次创建多个变量。
const result = /(a+)(b+)(c+)/.exec("aaabcc");
let [, a, b, c] = result;
console.log(a, b, c); // "aaa" "b" "cc"
有关更多信息,请参阅解构赋值。
规范
规范 |
---|
ECMAScript 语言规范 # sec-let-and-const-declarations |
浏览器兼容性
BCD 表格仅在浏览器中加载
另请参阅
var
const
- 提升
- ES6 深入:
let
和const
在 hacks.mozilla.org 上 (2015) let
和const
在 Firefox 44 中的重大更改 在 blog.mozilla.org 上 (2015)- 你不知道 JS:作用域和闭包,第 3 章:函数与块级作用域 由 Kyle Simpson 撰写
- 什么是时间死区? 在 Stack Overflow 上
- 使用
let
和var
之间的区别是什么? 在 Stack Overflow 上 - 为什么在 JavaScript 中选择 'let' 作为块级变量声明的名称? 在 Stack Overflow 上