此页面由社区从英文翻译而来。了解更多并加入 MDN Web Docs 社区。
let
Baseline Widely available
This feature is well established and works across many devices and browser versions. It’s been available across browsers since 2016年9月.
let 声明用于声明可重新赋值的块级作用域局部变量,并且可以选择将其初始化为一个值。
In this article
尝试一下
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声明在脚本的顶级作用域上声明变量时不会在全局对象上创建属性。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 关键字后方的列表叫做绑定列表,使用逗号分隔,其中的逗号不是逗号运算符,并且= 符号也不是赋值运算符。后初始化的变量能够引用列表中之前初始化的变量。
暂时性死区
用let、const 或class 声明的变量可以称其从代码块的开始一直到代码执行到变量声明的位置并被初始化前,都处于一个“暂时性死区”(Temporal dead zone,TDZ)中。
当变量处于暂时性死区之中时,其尚未被初始化,并且任何访问其的尝试都将导致抛出ReferenceError。当代码执行到变量被声明的位置时,变量会被初始化为一个值。如果变量声明中未指定初始值,则变量将被初始化为undefined。
这与var 声明的变量不同,如果在声明位置前访问var 声明的变量会返回undefined。以下代码演示了在声明位置前访问let 和var 声明的变量的不同结果。
{ // 暂时性死区始于作用域开头 console.log(bar); // "undefined" console.log(foo); // ReferenceError: Cannot access 'foo' before initialization var bar = 1; let foo = 2; // 暂时性死区结束(对 foo 而言)}使用“暂时性”一词是因为这个区域取决于代码执行的时间点,而不是代码编写的顺序。例如,下面的代码能够运行,是因为虽然使用let 变量的函数写在变量声明之前,但函数是在暂时性死区外面被调用的。
{ // 暂时性死区始于作用域开头 const func = () => console.log(letVar); // 没问题 // 在暂时性死区内访问 letVar 会抛出 `ReferenceError` let letVar = 3; // 暂时性死区结束(对 letVar 而言) func(); // 在暂时性死区外调用}在暂时性死区内对let 声明的变量使用typeof 运算符也会抛出ReferenceError:
typeof i; // ReferenceError: Cannot access 'i' before initializationlet i = 10;这与对未声明的变量和存放undefined 值的变量使用typeof 运算符不同:
console.log(typeof undeclaredVariable); // "undefined"备注:let 和const 声明仅在当前脚本被处理时才会被处理。如果在一个 HTML 中有两个以脚本模式运行的<script> 元素,那么第一个脚本不会受到第二个脚本中顶层let 或const 变量的暂时性死区限制,尽管如果你在第一个脚本中声明了一个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}如果你在交互式解释器中实验,比如 Firefox web 控制台(更多工具 >Web 开发者工具 >控制台),当你分开两次输入并运行含有同一个名称的let 声明时,你会得到同上的重复声明错误。有关更进一步的讨论参见这个议题:Firefox bug 1580891。Chrome 的控制台则允许在不同次的输入中重复声明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 声明的变量的作用域只在其声明的块或子块内部,在这一点上,它与var 非常相似。二者之间主要的区别在于var 声明的变量的作用域是整个闭合的函数。
function varTest() { var x = 1; { var x = 2; // 同一个变量! console.log(x); // 2 } console.log(x); // 2}function letTest() { let x = 1; { let x = 2; // 不同的变量 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暂时性死区和词法作用域
以下代码会导致ReferenceError:
function test() { var foo = 33; if (foo) { let foo = foo + 55; // ReferenceError }}test();由于外部变量var foo 有值,因此会执行if 语句块,但是由于词法作用域,该值在块内不可用:if 块内的标识符foo 是let foo。表达式foo + 55 会抛出ReferenceError 异常,因为let foo 还没完成初始化——它仍处于暂时性死区内。
在下面的情况中,这种现象可能会使你感到困惑。指令let n of n.a 已经处于for...of 循环块的作用域内,因此,标识符n.a 被解析为位于指令(let n)本身的n 对象的属性a。因为n 的声明尚未执行结束,它仍然处于暂时性死区内。
function go(n) { // n 在此处被定义! console.log(n); // { a: [1, 2, 3] } for (let n of n.a) { // ^ ReferenceError console.log(n); }}go({ a: [1, 2, 3] });其他情况
当let 被用在块中时,let 将变量的作用域限制在块内。这不同于var,var 的作用域为其被声明的函数内。
var a = 1;var b = 2;{ var a = 11; // 作用域为全局 let b = 22; // 作用域为块内 console.log(a); // 11 console.log(b); // 22}console.log(a); // 11console.log(b); // 2然而,下面的var 与let 连携的声明方式会抛出SyntaxError,因为var 并不限制在块作用域内,导致它们处于同一作用域。这会导致隐式地重复声明变量。
let x = 1;{ var x = 2; // 重复声明的语法错误}带有解构的声明
每个= 的左侧都可以是一个绑定模板,这使得能够一次性创建多个变量。
const result = /(a+)(b+)(c+)/.exec("aaabcc");let [, a, b, c] = result;console.log(a, b, c); // "aaa" "b" "cc"更多信息,参见解构。
规范
| Specification |
|---|
| ECMAScript® 2026 Language Specification> # sec-let-and-const-declarations> |
浏览器兼容性
参见
varconst- 变量提升
- 深入 ES6:
let和const——hacks.mozilla.org(2015) - Firefox 44 中
let和const的破坏性变更——blog.mozilla.org(2015) - 你不懂 JS:作用域和闭包 第三章:函数对决块作用域——Kyle Simpson
- 什么是暂时性死区?——Stack Overflow
let和var在用法上有什么不同?——Stack Overflow- 为什么“let”被选为了 JavaScript 块作用域变量声明的名字?——Stack Overflow