摘要:在本教程中,您将学习如何使用 JavaScript let
关键字来声明块级作用域变量。
JavaScript let 关键字简介
在 ES5 中,当您使用 var
关键字声明变量时,变量的作用域要么是全局的,要么是局部的。如果您在函数外部声明变量,则该变量的作用域是全局的。当您在函数内部声明变量时,该变量的作用域是局部的。
ES6 提供了一种使用 let
关键字声明变量的新方法。let
关键字类似于 var
关键字,只是这些变量是块级作用域。例如
let variable_name;
Code language: JavaScript (javascript)
在 JavaScript 中,块由花括号 {}
表示,例如,if else
、for
、do while
、while
、try catch
等等。
if(condition) {
// inside a block
}
Code language: JavaScript (javascript)
请看以下示例
let x = 10;
if (x == 10) {
let x = 20;
console.log(x); // 20: reference x inside the block
}
console.log(x); // 10: reference at the begining of the script
Code language: JavaScript (javascript)
脚本如何工作
- 首先,声明一个名为
x
的变量,并将其值初始化为 10。 - 其次,在
if
块内部声明一个与x
同名的变量,但初始值为 20。 - 第三,在
块内和之后输出变量if
x
的值。
由于 let
关键字声明了一个块级作用域变量,因此 if
块内的 x
变量是一个新变量,它会遮蔽在脚本顶部声明的 x
变量。因此,控制台中 x
的值为 20
。
当 JavaScript 引擎完成 if
块的执行后,if
块内的 x
变量将超出作用域。因此,if
块后面的 x
变量的值为 10。
JavaScript let 和全局对象
当您使用 var
关键字声明全局变量时,您会将该变量添加到 全局对象 的属性列表中。在 Web 浏览器的情况下,全局对象是 window
。例如
var a = 10;
console.log(window.a); // 10
Code language: JavaScript (javascript)
但是,当您使用 let
关键字声明变量时,该变量不会作为属性附加到全局对象。例如
let b = 20;
console.log(window.b); // undefined
Code language: JavaScript (javascript)
JavaScript let 和 for 循环中的回调函数
请看以下示例。
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
Code language: JavaScript (javascript)
代码的目的是每秒将从 0 到 4 的数字输出到控制台。但是,它五次输出数字 5
5
5
5
5
5
在此示例中,变量 i
是一个全局变量。循环结束后,它的值为 5。当传递给 setTimeout()
函数的回调函数执行时,它们引用的是具有值 5 的同一个变量 i
。
在 ES5 中,您可以通过创建一个新的作用域来解决此问题,以便每个回调函数引用一个新的变量。要创建一个新的作用域,您需要创建一个函数。通常,您会使用以下的 IIFE 模式
for (var i = 0; i < 5; i++) {
(function (j) {
setTimeout(function () {
console.log(j);
}, 1000);
})(i);
}
Code language: JavaScript (javascript)
输出
0
1
2
3
4
在 ES6 中,let
关键字在每次循环迭代中声明一个新的变量。因此,您只需将 var
关键字替换为 let
关键字即可解决此问题
for (let i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
Code language: JavaScript (javascript)
为了使代码完全符合 ES6 风格,您可以使用 箭头函数,如下所示
for (let i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 1000);
}
Code language: JavaScript (javascript)
请注意,您将在后面的教程中了解有关 箭头函数的更多信息。
重新声明
var
关键字允许您重新声明变量,没有任何问题
var counter = 0;
var counter;
console.log(counter); // 0
Code language: JavaScript (javascript)
但是,使用 let
关键字重新声明变量会导致错误
let counter = 0;
let counter;
console.log(counter);
Code language: JavaScript (javascript)
以下是错误消息
Uncaught SyntaxError: Identifier 'counter' has already been declared
Code language: JavaScript (javascript)
JavaScript let 变量和提升
让我们检查以下示例
{
console.log(counter); //
let counter = 10;
}
Code language: JavaScript (javascript)
这段代码会引发错误
Uncaught ReferenceError: Cannot access 'counter' before initialization
Code language: JavaScript (javascript)
在此示例中,在声明 counter
变量之前访问它会导致 ReferenceError
。您可能认为使用 let
关键字声明的变量不会提升,但实际上会。
实际上,JavaScript 引擎会将使用 let
关键字声明的变量提升到块的顶部。但是,JavaScript 引擎不会初始化该变量。因此,当您引用未初始化的变量时,您会得到 ReferenceError
。
暂时性死亡区域 (TDZ)
使用 let
关键字声明的变量有一个所谓的暂时性死亡区域 (TDZ)。TDZ 是从块开始到处理变量声明的时间段。
以下示例说明了暂时性死亡区域是基于时间的,而不是基于位置的。
{ // enter new scope, TDZ starts
let log = function () {
console.log(message); // messagedeclared later
};
// This is the TDZ and accessing log
// would cause a ReferenceError
let message= 'Hello'; // TDZ ends
log(); // called outside TDZ
}
Code language: JavaScript (javascript)
在此示例中
首先,花括号开始了一个新的块级作用域,因此,TDZ 开始了。
其次,log()
函数表达式访问 message
变量。但是,log()
函数尚未执行。
第三,声明 message
变量并将其值初始化为 'Hello'
。从块级作用域开始到访问 message
变量的时间被称为暂时性死亡区域。当 JavaScript 引擎处理声明时,TDZ 结束。
最后,调用 log()
函数,该函数在 TDZ 外部访问 message
变量。
请注意,如果您在 TDZ 中访问使用 let
关键字声明的变量,您会得到 ReferenceError
,如以下示例所示。
{ // TDZ starts
console.log(typeof myVar); // undefined
console.log(typeof message); // ReferenceError
let message; // TDZ ends
}
Code language: JavaScript (javascript)
注意,myVar
变量是一个不存在的变量,因此它的类型是 undefined。
暂时性死亡区域可以防止您在声明变量之前意外地引用该变量。
总结
- 使用
let
关键字声明的变量是块级作用域的,不会被初始化为任何值,并且不会附加到全局对象。 - 使用
let
关键字重新声明变量会导致错误。 - 使用
let
关键字声明的变量的暂时性死亡区域从块开始到初始化被求值的时间段。