ES6 新增了`let`命令,用来声明变量。它的用法类似于`var`,但是所声明的变量,只在`let`命令所在的代码块内有效。
## 1\. 块级作用域
* 示例代码一
~~~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);
}
}
~~~
## 2\. 不允许重复声明
~~~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`不允许在相同作用域内,重复声明同一个变量
## 3\. 不存在变量提升
`var`命令会发生“变量提升”现象,即变量可以在声明之前使用,值为`undefined`。这种现象多多少少是有些奇怪的,按照一般的逻辑,变量应该在声明语句之后才可以使用。
~~~javascript
console.log(hello); // undefined
var hello = "hello";
console.log(world); // ReferenceError: Cannot access 'world' before initialization
let world = "world";
~~~
## 4\. 暂时性死区
只要块级作用域内存在`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)。