let

let 声明声明可重新赋值的、块级作用域的局部变量,可以选择性地将每个变量初始化为一个值。

尝试一下

语法

js
let name1;
let name1 = value1;
let name1 = value1, name2 = value2;
let name1, name2 = value2;
let name1 = value1, name2, /* …, */ nameN = valueN;

参数

nameN

要声明的变量的名称。每个名称必须是合法的 JavaScript 标识符解构绑定模式

valueN 可选

变量的初始值。它可以是任何合法的表达式。默认值为 undefined

描述

let 声明的变量的作用域是以下最靠近 let 声明的某个大括号封闭的语法

或者,如果以上都不适用

  • 对于在模块模式下运行的代码,则为当前 模块
  • 对于在脚本模式下运行的代码,则为全局作用域。

var 相比,let 声明有以下区别

  • let 声明的作用域是块级作用域,以及函数级作用域。
  • let 声明只能在到达声明的位置后才能访问(见 暂时性死区)。因此,let 声明通常被认为是 未提升 的。
  • let 声明不会在脚本顶层声明时在 globalThis 上创建属性。
  • let 声明不能在相同作用域中被其他任何声明 重新声明
  • let 开始 声明,而不是语句。这意味着不能使用单独的 let 声明作为块的主体(这很有道理,因为没有办法访问该变量)。
    js
    if (true) let a = 1; // SyntaxError: Lexical declaration cannot appear in a single-statement context
    

请注意,let非严格模式 中用 varfunction 声明时是允许作为标识符名称使用的,但应避免使用 let 作为标识符名称,以防止出现意外的语法歧义。

许多样式指南(包括 MDN 的指南)建议在变量在作用域中没有被重新赋值时使用 const 而不是 let。这明确表明变量的类型(或原始类型的值)永远不会改变。其他人可能更喜欢使用 let 来表示被修改的非原始类型。

let 关键字后的列表称为绑定列表,并由逗号分隔,其中逗号不是 逗号运算符= 符号不是 赋值运算符。后面变量的初始化器可以引用列表中前面的变量。

暂时性死区 (TDZ)

letconstclass 声明的变量从块的开头到代码执行到达声明和初始化该变量的位置,被认为处于“暂时性死区”(TDZ) 中。

在 TDZ 内,该变量尚未初始化为某个值,任何访问该变量的尝试都会导致 ReferenceError。当执行到达代码中声明该变量的位置时,该变量将被初始化为某个值。如果在变量声明中没有指定初始值,则它将被初始化为 undefined 值。

这与 var 变量不同,如果在声明之前访问 var 变量,则它将返回 undefined 值。以下代码演示了在声明之前访问 letvar 时结果的不同。

js
{
  // 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 外部调用的。

js
{
  // 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

js
{
  typeof i; // ReferenceError: Cannot access 'i' before initialization
  let i = 10;
}

这与对未声明的变量和值为 undefined 的变量使用 typeof 不同。

js
console.log(typeof undeclaredVariable); // "undefined"

注意: letconst 声明仅在当前脚本被处理时才会被处理。如果您在一个 HTML 中有两个在脚本模式下运行的 <script> 元素,则第一个脚本不受第二个脚本中声明的顶层 letconst 变量的 TDZ 限制,但如果您在第一个脚本中声明了 letconst 变量,则在第二个脚本中再次声明它会导致 重新声明错误

重新声明

let 声明不能与其他任何声明位于相同的作用域中,包括 letconstclassfunctionvarimport 声明。

js
{
  let foo;
  let foo; // SyntaxError: Identifier 'foo' has already been declared
}

函数主体内的 let 声明不能与参数同名。catch 块内的 let 声明不能与 catch 绑定的标识符同名。

js
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 语句中遇到错误,因为只有一个代码块。

js
let x = 1;

switch (x) {
  case 0:
    let foo;
    break;
  case 1:
    let foo; // SyntaxError: Identifier 'foo' has already been declared
    break;
}

为避免错误,请将每个 case 包裹在一个新的代码块语句中。

js
let x = 1;

switch (x) {
  case 0: {
    let foo;
    break;
  }
  case 1: {
    let foo;
    break;
  }
}

示例

作用域规则

let 声明的变量在其声明的代码块以及任何包含的子代码块中都有作用域。这样,let 的工作方式与 var 非常相似。主要区别在于 var 变量的作用域是整个封闭函数。

js
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
}

在程序和函数的顶层,letvar 不同,不会在全局对象上创建属性。例如

js
var x = "global";
let y = "global";
console.log(this.x); // "global"
console.log(this.y); // undefined

TDZ 与词法作用域结合

以下代码在显示的行导致 ReferenceError

js
function test() {
  var foo = 33;
  if (foo) {
    let foo = foo + 55; // ReferenceError
  }
}
test();

if 代码块被评估,因为外部 var foo 有一个值。但是,由于词法作用域,此值在代码块内部不可用:if 代码块内部的标识符 foolet foo。表达式 foo + 55 抛出 ReferenceError,因为 let foo 的初始化尚未完成——它仍然处于时间死区。

这种现象在以下情况下可能会令人困惑。指令 let n of n.a 已经在 for...of 循环的代码块的作用域内。因此,标识符 n.a 解析为指令本身(let n)第一部分中 n 对象的属性 a。这仍然处于时间死区,因为其声明语句尚未到达并终止。

js
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 将变量的作用域限制为该代码块。注意 varlet 之间的区别,var 的作用域在它被声明的函数内。

js
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

但是,以下 varlet 声明组合是一个SyntaxError,因为 var 不是块级作用域,导致它们处于相同的作用域。这会导致变量的隐式重新声明。

js
let x = 1;

{
  var x = 2; // SyntaxError for re-declaration
}

使用解构声明

每个 = 的左侧也可以是一个绑定模式。这允许一次创建多个变量。

js
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 表格仅在浏览器中加载

另请参阅