在本教程中,您将学习如何使用 JavaScript let关键字来声明块级作用域变量。

JavaScript let 关键字介绍

在 ES5 ,当你使用 var 关键字声明一个变量时,变量的作用域要么是全局的,要么是局部的。

如果在函数外部声明变量,则变量的作用域是全局的。当您在函数内声明变量时,变量的作用域是局部的。

ES6 提供一种使用 let 关键字声明变量的新方法。let 关键字与 var 关键字类似,只是 let 关键字声明的变量是块级作用域的。

例如:

let variable_name;

在 JavaScript ,块用花括号 {} 表示,例如 if else,  for, do while, while,try catch等等。

if(condition) {
   // 块级作用域内
}

请参阅以下示例:

let x = 10;
if (x == 10) {
    let x = 20;
    console.log(x); // 20
}
console.log(x); // 10

代码如何工作:

  • 首先,声明一个变量 x 并将其值初始化为 10。
  • 其次,在 if 块内声明一个同名的变量 x 但初始值为 20 。
  • 最后,打印 if 块内和if 块外的 x 变量的值。

因为 let 关键字声明一个块级作用域的变量,if 块内的 x 变量是一个新的变量,它掩盖了在脚本顶部声明的  x 变量。因此,控制台中的打印的值是 20

当 JavaScript 引擎执行 if 块完成时,if 块内的 x 变量不在全局范围。因此,if 后面的 x变量的值是 10。

JavaScript let 和全局对象

当您使用 var 关键字声明一个全局变量时,您将该变量添加到全局对象的属性列表中。对于 Web 浏览器,全局对象是window。 例如:

var a = 10;
console.log(window.a); // 10

但是,当您使用 let 关键字声明变量时,该变量不会作为属性添加到全局对象。例如:

let b = 20;
console.log(window.b); // undefined

JavaScript  for 循环的 let 和回调函数  

请阅读以下示例。

for (var i = 0; i < 5; i++) {
    setTimeout(function () {
        console.log(i);
    }, 1000);
}

代码的目的是每秒向控制台打印从 0 到 4 的数字。但是,它打印五次数字 5

5
5
5
5
5

在这个例子中,i 变量是一个全局变量。循环后,它的值是 5。当回调函数传递给函数 setTimeout() 执行时,它们引用同一个变量 i,值是 5。

在 ES5 ,您可以创建另一个作用域来解决此问题,使每个回调函数都引用一个新变量。要创建一个新的作用域,您需要创建一个函数。通常,您使用 IIFE 模式创建新的作用域。

如下:

for (var i = 0; i < 5; i++) {
    (function (j) {
        setTimeout(function () {
            console.log(j);
        }, 1000);
    })(i);
}

输出:

0
1
2
3
4

在 ES6 ,let 关键字在每次循环迭代中声明一个新变量。因此,您只需将 var 关键字替换为 let 关键字即可解决问题:

for (let i = 0; i < 5; i++) {
    setTimeout(() => console.log(i), 1000);
}

为了使代码完全符合 ES6 风格,可以使用箭头函数,如下所示:

var counter = 0;
var counter;
console.log(counter); // 0

重新声明

var 关键字允许您随时地重新声明变量:

var counter = 0;
var counter;
console.log(counter); // 0

但是,使用 let 关键字重新声明变量会导致错误:

let counter = 0;
let counter;
console.log(counter);

JavaScript 引擎抛出错误消息 Uncaught SyntaxError: Identifier 'counter' has already been declared。翻译过来就是“未捕获的语法错误,标识符 counter 已经被声明”。

Uncaught SyntaxError: Identifier 'counter' has already been declared

JavaScript let 变量和提升

请查看以下示例:

{
    console.log(counter); // 
    let counter = 10;    
}

此代码,JavaScript 引擎会抛出错误 Uncaught ReferenceError: Cannot access 'counter' before initialization,翻译过来就是“未捕获的引用错误,初始化之前不能访问 counter”:

Uncaught ReferenceError: Cannot access 'counter' before initialization

在此示例中,在声明 counter 变量之前访问 counter 变量会导致 ReferenceError。您可能认为使用 let 关键字的变量声明不会提升,但它会提升的。

事实上,JavaScript 引擎会将 let 关键字声明的变量提升到块级作用域的顶部。但是,JavaScript 引擎不会初始化此变量。因此,当你引用一个未初始化的变量时,你会得到一个  ReferenceError

暂时性死区  TDZ

let 关键字声明的变量有一个所谓的暂时性死区 TDZ 。TDZ 是从块开始到处理变量声明的时间。

下面的示例说明暂时性死区是基于时间的,而不是基于位置的

{ // 进入新的作用域, TDZ 开始
    let log = function () {
        console.log(message); // message 变量在后面声明
    };

    // This is the TDZ and accessing log
    // would cause a ReferenceError

    let message= 'Hello'; // TDZ 结束
    log(); // 调用log 函数在 TDZ 外部
}

在这个例子中:

首先,大括号创建了一个新的块级作用域,因此,TDZ 开始。其次,log() 函数访问 message 变量。此时,log() 函数尚未执行。

第三,声明 message 变量并将其值初始化为 'Hello'。从块级作用域开始到 message 变量被访问的时间称为临时死亡区。当 JavaScript 引擎处理声明 message 变量时,TDZ 结束。

最后,调用 log() 函数并访问 TDZ 外部的 message 变量。

请注意,如果您访问一个变量是在 TDZ 内并使用 let 关键字声明的,JavaScript 引擎 将会抛出 ReferenceError

如下所示

{ // TDZ 开始
    console.log(typeof myVar); // undefined
    console.log(typeof message); // ReferenceError
    let message; // TDZ 结束
}

注意 myVar 变量是一个不存在的变量,因此它的类型是 undefined。临时死亡区可防止您在变量声明之前意外引用该变量。

结论

使用 let 关键字声明的变量是在块级作用域的,变量并没有被初始化为任何值,也没有添加到全局对象。

使用 let 关键字重新声明变量将会导致错误。 let 关键字声明的变量的暂时性死区从块开始,直到变量被初始化。