ES6 新增了`let`命令,用來聲明變量。它的用法類似于`var`,但是所聲明的變量,只在`let`命令所在的代碼塊內有效。
## 一、塊級作用域
* 示例代碼一
~~~javascript
{
var height = "170CM";
let weight = "60KG";
console.log("身高為" + height); // 身高為170CM
console.log("體重為" + weight); // 體重為60KG
}
console.log("身高為" + height); // 身高為170CM
console.log("體重為" + weight); // ReferenceError: height is not defined
~~~
在上面代碼中,分別用`let`和`var`聲明了兩個變量。然后在代碼塊之外調用這兩個變量,結果`let`聲明的變量報錯,`var`聲明的變量返回了正確的值。這表明,`let`聲明的變量只在它所在的代碼塊有效。
* 示例代碼二
~~~javascript
for (var i = 0; i < 10; i++) {
// do something ...
}
console.log(i); // 10
~~~
~~~javascript
for (let i = 0; i < 10; i++) {
// do something ...
}
console.log(i);
// ReferenceError: i is not defined
~~~
在上面代碼中,分別用`let`和`var`聲明了`for`循環的計數器變量`i`。然后在循環結束之后調用循環計算器`i`,`var`聲明的變量值為循環終止的值,`let`聲明的變量報錯。這表明,`var`聲明的變量在`for`循環結束泄露到全局中,而`let`聲明的變量只在它所在的代碼塊有效。
* 示例代碼三
~~~html
<button class="btn">按鈕一</button>
<button class="btn">按鈕二</button>
<button class="btn">按鈕三</button>
<button class="btn">按鈕四</button>
<button class="btn">按鈕五</button>
~~~
~~~javascript
var btns = document.getElementsByClassName("btn");
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = function() {
alert(i);
}
}
~~~
頁面上有5個按鈕,給每一個按鈕綁定點擊事件,彈出警告框顯示按鈕的索引值,結果每一個按鈕彈出的警告框都顯示5。
解決辦法如下:
~~~javascript
// 方法一:閉包
var btns = document.getElementsByClassName("btn");
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = (function(index) {
return function() {
alert(index);
}
})(i);
}
// 方法二:let
let btns = document.getElementsByClassName("btn");
for (let i = 0; i < btns.length; i++) {
btns[i].onclick = function() {
alert(i);
}
}
~~~
## 二、不允許重復聲明
~~~javascript
var name = "tom";
var name = "jerry";
let price = 12.5;
var price = 15;
// SyntaxError: Identifier 'price' has already been declared
let age = 20;
let age = 25;
// SyntaxError: Identifier 'age' has already been declared
~~~
`let`不允許在相同作用域內,重復聲明同一個變量
## 三、不存在變量提升
`var`命令會發生“變量提升”現象,即變量可以在聲明之前使用,值為`undefined`。這種現象多多少少是有些奇怪的,按照一般的邏輯,變量應該在聲明語句之后才可以使用。
~~~javascript
console.log(hello); // undefined
var hello = "hello";
console.log(world); // ReferenceError: Cannot access 'world' before initialization
let world = "world";
~~~
## 四、暫時性死區
只要塊級作用域內存在`let`命令,它所聲明的變量就“綁定”(binding)這個區域,不再受外部的影響。
~~~javascript
var temp = 100;
if (true) {
temp = "hello"; // ReferenceError: Cannot access 'temp' before initialization
let temp;
}
~~~
上面代碼中,存在全局變量 temp,但是塊級作用域內`let`又聲明了一個局部變量 temp,導致后者綁定這個塊級作用域,所以在`let`聲明變量前,對 temp 賦值會報錯。
ES6 明確規定,如果區塊中存在`let`和`const`命令,這個區塊對這些命令聲明的變量,從一開始就形成了封閉作用域。凡是在聲明之前就使用這些變量,就會報錯。
總之,在代碼塊內,使用`let`命令聲明變量之前,該變量都是不可用的。這在語法上,稱為“暫時性死區”(temporal dead zone,簡稱 TDZ)。