let
let 声明用于声明可重新赋值的块级作用域局部变量,可以选择将其初始化为一个值。
试一试
let x = 1;
if (x === 1) {
  let x = 2;
  console.log(x);
  // Expected output: 2
}
console.log(x);
// Expected output: 1
语法
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声明不会在globalThis上创建属性。
- 
let声明不能在同一作用域内被任何其他声明重新声明。
- 
let开启的是_声明_,而不是_语句_。这意味着你不能将独立的let声明用作块的主体(这是有道理的,因为没有办法访问该变量)。jsif (true) let a = 1; // SyntaxError: Lexical declaration cannot appear in a single-statement context
请注意,在非严格模式下,当使用 var 或 function 声明时,let 允许作为标识符名称,但你应该避免使用 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 Web 控制台(工具 > Web 开发者 > Web 控制台))中进行实验,并在两个不同的输入中运行两个同名的 let 声明,你可能会得到相同的重新声明错误。有关此问题的进一步讨论,请参阅 Firefox bug 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 的区别,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® 2026 语言规范 # sec-let-and-const-declarations | 
浏览器兼容性
加载中…
另见
- var
- const
- 提升
- ES6 深入:let和const,发表于 hacks.mozilla.org (2015)
- Firefox 44 中 let和const的突破性变化,发表于 blog.mozilla.org (2015)
- 你不知道的 JS:作用域与闭包,第三章:函数作用域 vs. 块级作用域,作者 Kyle Simpson
- 什么是暂时性死区?,发表于 Stack Overflow
- 使用 let和var有什么区别?,发表于 Stack Overflow
- JavaScript 中为什么选择 'let' 作为块级作用域变量声明的名称?,发表于 Stack Overflow