let

let 声明声明可重新分配的块范围局部变量,可以选择将每个变量初始化为一个值。

¥The let declaration declares re-assignable, block-scoped local variables, optionally initializing each to a value.

Try it

语法

¥Syntax

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

参数

¥Parameters

nameN

要声明的变量的名称。每个都必须是合法的 JavaScript identifier解构结合模式

valueN Optional

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

描述

¥Description

let 声明的变量的范围是以下大括号括起来的语法之一,最接近地包含 let 声明:

¥The scope of a variable declared with let is one of the following curly-brace-enclosed syntaxes that most closely contains the let declaration:

或者如果以上都不适用:

¥Or if none of the above applies:

  • 当前的 module,用于在模块模式下运行的代码
  • 全局范围,用于在脚本模式下运行的代码。

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

¥Compared with var, let declarations have the following differences:

  • let 声明的作用域为块和函数。
  • let 声明只能在到达声明位置后才能访问(参见 颞死区)。因此,let 声明通常被视为 non-hoisted
  • 当在脚本的顶层声明时,let 声明不会在 globalThis 上创建属性。
  • let 声明不能被同一范围内的任何其他声明成为 redeclared
  • let 开始 声明,而不是声明。这意味着你不能使用单独的 let 声明作为块的主体(这是有道理的,因为无法访问该变量)。
    js
    if (true) let a = 1; // SyntaxError: Lexical declaration cannot appear in a single-statement context
    

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

¥Note that let is allowed as an identifier name when declared with var or function in non-strict mode, but you should avoid using let as an identifier name to prevent unexpected syntax ambiguities.

许多风格指南(包括 MDN 的)建议在变量未在其作用域中重新分配时使用 const 而不是 let。这使得意图变得清晰:变量的类型(或值,在原语的情况下)永远不会改变。其他人可能更喜欢 let 作为突变的非基元。

¥Many style guides (including MDN's) recommend using const over let whenever a variable is not reassigned in its scope. This makes the intent clear that a variable's type (or value, in the case of a primitive) can never change. Others may prefer let for non-primitives that are mutated.

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

¥The list that follows the let keyword is called a binding list and is separated by commas, where the commas are not comma operators and the = signs are not assignment operators. Initializers of later variables can refer to earlier variables in the list.

颞死区 (TDZ)

¥Temporal dead zone (TDZ)

使用 letconstclass 声明的变量被称为从块开始到代码执行到达声明和初始化变量的位置位于 "颞死区" (TDZ) 中。

¥A variable declared with let, const, or class is said to be in a "temporal dead zone" (TDZ) from the start of the block until code execution reaches the place where the variable is declared and initialized.

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

¥While inside the TDZ, the variable has not been initialized with a value, and any attempt to access it will result in a ReferenceError. The variable is initialized with a value when execution reaches the place in the code where it was declared. If no initial value was specified with the variable declaration, it will be initialized with a value of undefined.

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

¥This differs from var variables, which will return a value of undefined if they are accessed before they are declared. The code below demonstrates the different result when let and var are accessed in code before the place where they are declared.

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

使用术语 "temporal" 是因为该区域取决于执行顺序(时间)而不是代码写入顺序(位置)。例如,下面的代码之所以有效,是因为即使使用 let 变量的函数出现在声明该变量之前,该函数也会在 TDZ 之外被调用。

¥The term "temporal" is used because the zone depends on the order of execution (time) rather than the order in which the code is written (position). For example, the code below works because, even though the function that uses the let variable appears before the variable is declared, the function is called outside the 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 中的 let 变量使用 typeof 运算符将抛出 ReferenceError

¥Using the typeof operator for a let variable in its TDZ will throw a ReferenceError:

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

这与使用 typeof 来表示未声明的变量以及保存 undefined 值的变量不同:

¥This differs from using typeof for undeclared variables, and variables that hold a value of undefined:

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

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

¥Note: let and const declarations are only processed when the current script gets processed. If you have two <script> elements running in script mode within one HTML, the first script is not subject to the TDZ restrictions for top-level let or const variables declared in the second script, although if you declare a let or const variable in the first script, declaring it again in the second script will cause a redeclaration error.

重新声明

¥Redeclarations

let 声明不能与任何其他声明处于同一范围,包括 letconstclassfunctionvarimport 声明。

¥let declarations cannot be in the same scope as any other declaration, including let, const, class, function, var, and import declaration.

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

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

¥A let declaration within a function's body cannot have the same name as a parameter. A let declaration within a catch block cannot have the same name as the catch-bound identifier.

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 Web 控制台(工具 > Web 开发者 > Web 控制台),并且在两个单独的输入中运行两个具有相同名称的 let 声明,则可能会遇到相同的重新声明错误。参见 火狐错误 1580891 中对此问题的进一步讨论。Chrome 控制台允许不同 REPL 输入之间进行 let 重新声明。

¥If you're experimenting in a REPL, such as the Firefox web console (Tools > Web Developer > Web Console), and you run two let declarations with the same name in two separate inputs, you may get the same re-declaration error. See further discussion of this issue in Firefox bug 1580891. The Chrome console allows let re-declarations between different REPL inputs.

你可能会在 switch 语句中遇到错误,因为只有一个块。

¥You may encounter errors in switch statements because there is only one block.

js
let x = 1;

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

为了避免该错误,请将每个 case 封装在新的块语句中。

¥To avoid the error, wrap each case in a new block statement.

js
let x = 1;

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

示例

¥Examples

范围规则

¥Scoping rules

let 声明的变量在声明它们的块以及任何包含的子块中具有其作用域。这样一来,let 的工作方式就很像 var 了。主要区别在于 var 变量的作用域是整个封闭函数:

¥Variables declared by let have their scope in the block for which they are declared, as well as in any contained sub-blocks. In this way, let works very much like var. The main difference is that the scope of a var variable is the entire enclosing function:

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 不同,不会在全局对象上创建属性。例如:

¥At the top level of programs and functions, let, unlike var, does not create a property on the global object. For example:

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

TDZ 与词法作用域相结合

¥TDZ combined with lexical scoping

以下代码会在所示行中生成 ReferenceError

¥The following code results in a ReferenceError at the line shown:

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 的初始化尚未完成 — 它仍处于临时死区中。

¥The if block is evaluated because the outer var foo has a value. However due to lexical scoping this value is not available inside the block: the identifier foo inside the if block is the let foo. The expression foo + 55 throws a ReferenceError because initialization of let foo has not completed — it is still in the temporal dead zone.

在如下情况下,这种现象可能会令人困惑。指令 let n of n.a 已经在 for...of 循环块的范围内。因此,标识符 n.a 被解析为位于指令本身第一部分 (let n) 的 n 对象的属性 a。由于其声明声明尚未到达并终止,因此仍处于暂时死区。

¥This phenomenon can be confusing in a situation like the following. The instruction let n of n.a is already inside the scope of the for...of loop's block. So, the identifier n.a is resolved to the property a of the n object located in the first part of the instruction itself (let n). This is still in the temporal dead zone as its declaration statement has not been reached and terminated.

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] });

其他情况

¥Other situations

当在块内使用时,let 将变量的范围限制为该块。请注意 var 之间的区别,其作用域位于声明它的函数内部。

¥When used inside a block, let limits the variable's scope to that block. Note the difference between var, whose scope is inside the function where it is declared.

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 不是块作用域,导致它们位于同一作用域中。这会导致变量的隐式重新声明。

¥However, this combination of var and let declarations below is a SyntaxError because var not being block-scoped, leading to them being in the same scope. This results in an implicit re-declaration of the variable.

js
let x = 1;

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

带有解构的声明

¥Declaration with destructuring

每个 = 的左侧也可以是装订图案。这允许一次创建多个变量。

¥The left-hand side of each = can also be a binding pattern. This allows creating multiple variables at once.

js
const result = /(a+)(b+)(c+)/.exec("aaabcc");
let [, a, b, c] = result;
console.log(a, b, c); // "aaa" "b" "cc"

欲了解更多信息,请参阅 解构赋值

¥For more information, see Destructuring assignment.

规范

Specification
ECMAScript Language Specification
# sec-let-and-const-declarations

¥Specifications

浏览器兼容性

BCD tables only load in the browser

¥Browser compatibility

也可以看看