let
let
声明声明可重新分配的块范围局部变量,可以选择将每个变量初始化为一个值。
¥The let
declaration declares re-assignable, block-scoped local variables, optionally initializing each to a value.
Try it
语法
参数
¥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:
- 堵塞 声明
switch
声明try...catch
声明for
语句之一 的主体(如果let
位于语句的标头中)- 函数体
- 静态初始化块
或者如果以上都不适用:
¥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
声明作为块的主体(这是有道理的,因为无法访问该变量)。jsif (true) let a = 1; // SyntaxError: Lexical declaration cannot appear in a single-statement context
请注意,当在 非严格模式 中与 var
或 function
一起声明时,允许使用 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)
使用 let
、const
或 class
声明的变量被称为从块开始到代码执行到达声明和初始化变量的位置位于 "颞死区" (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
。下面的代码演示了在声明 let
和 var
的位置之前的代码中访问 let
和 var
时的不同结果。
¥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.
{
// 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.
{
// 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
:
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
:
console.log(typeof undeclaredVariable); // "undefined"
注意:仅当处理当前脚本时才会处理
let
和const
声明。如果在一个 HTML 中以脚本模式运行两个<script>
元素,则第一个脚本不受第二个脚本中声明的顶层let
或const
变量的 TDZ 限制,但如果你在第一个脚本中声明let
或const
变量,则第一个脚本不受 TDZ 限制。 脚本中,在第二个脚本中再次声明它会导致 重新声明错误。¥Note:
let
andconst
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-levellet
orconst
variables declared in the second script, although if you declare alet
orconst
variable in the first script, declaring it again in the second script will cause a redeclaration error.
重新声明
¥Redeclarations
let
声明不能与任何其他声明处于同一范围,包括 let
、const
、class
、function
、var
和 import
声明。
¥let
declarations cannot be in the same scope as any other declaration, including let
, const
, class
, function
, var
, and import
declaration.
{
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.
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.
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.
let x = 1;
switch (x) {
case 0: {
let foo;
break;
}
case 1: {
let foo;
break;
}
}
示例
范围规则
¥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:
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
不同,不会在全局对象上创建属性。例如:
¥At the top level of programs and functions, let
, unlike var
, does not create a property on the global object. For example:
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:
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
的初始化尚未完成 — 它仍处于临时死区中。
¥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.
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.
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
不是块作用域,导致它们位于同一作用域中。这会导致变量的隐式重新声明。
¥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.
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.
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 |
浏览器兼容性
BCD tables only load in the browser
也可以看看
¥See also
var
const
- 提升
- hacks.mozilla.org 上的 ES6 深入了解:
let
和const
(2015) - blog.mozilla.org 上的 Firefox 44 中
let
和const
的重大变化 (2015) - 凯尔·辛普森的《你不知道的 JS:范围和关闭,第 3 章:函数与块作用域》
- 堆栈溢出上的 什么是颞死区?
- 堆栈溢出上的 使用
let
和var
有什么区别? - 堆栈溢出上的 为什么 JavaScript 中的块作用域变量声明选择名称 'let'?