let命令的用法与var类似,用于声明一个变量,但是let声明的变量只能在let所在的代码块内有效:
let 声明的变量只在其所在代码块之内有效。
var的不足之处
1 | var arr = [ ]; |
- 每一个元素是一个函数,运行后弹出相对应的数字,比如:运行arr8;想alert出一个数字8,运行arr1; 想alert出一个数字1,依次类推。但是结果并不是我们预想的那样。运行后实际弹出的是10;我们知道了var有不足的地方。
用let替换var后1
2
3
4
5
6
7var arr = [ ];
for(let i=0; i<10; i++){
arr[i] = function(){
alert(i)
}
}
arr[8](); //结果:8 - 对比一下两段代码,唯一的不同之处就是循环的时候初始化变量 i 是使用let,而不是用var,运行arr8后确实弹出了数字8;如果运行的是arr3,就会弹出数字3;
为什么用let就可以,用var就跑偏了呢?这是因为let声明的变量仅仅在自己的块级作用域起作用,出了这个块级作用域就不起作用。
任何一对花括号(这玩意:{ })中的语句都属于一个块,在花括号里面用let定义的所有变量在花括号外都是不可见的,我们称之为块级作用域。
var存在变量提升
1 | var a = 1; |
- JavaScript引擎会将上面的代码更改成这样
1
2
3
4
5
6var a = 1;
(function(){
var a;
alert(a);
a = 2;
})();//结果:undefined - 对比一下两段简短的代码:var a = 2; 这句代码被拆分成两部分:声明var a ; 和 定义a = 2;而声明部分被提升(看到了吗?提升两个字出现了)到了代码块的前面,运行的时候自己挪到前面了,这就是“变量提升“,结果就是:先执行声明,接着就执行alert(a);变量a只是声明还没定义,就弹出了undefined了。
所以,归根结底就是“变量提升“在作怪。这就是var的又一大不足之处。那么,用let关键字在代码块就不会被提升了吗?是的,不提升了。用let关键字来定义a;这样a在代码块内就不会提升了。那为什么又报错了呢,因为用let声明的变量,在其块级作用域内是封闭的,是不会受到外面的全局变量a影响的,并且要先声明再使用,所以a的值即不是1(因为不受外面的影响),也不是undefined(因为先声明后使用),更不是2,未声明定义就使用,只有报错啦。1
2
3
4
5var a = 1;
(function(){
alert(a);
let a = 2;
})(); // 结果:a is not defined 报错a未定义用let关键字也算是提醒我们,平时记得先声明定义再使用的好习惯。
暂时性死区
- 如果区块中存在let和const命令,则这个区块对这些命令声明的变量从一开始就形成封闭作用域。只要在声明之前就使用这些变量就会报错。这在语法上称为”暂时性死区”简称TDZ。正确代码
1
2
3
4
5
6if(true){
tmp = 'abc';//TDZ开始
let tmp;//TDZ结束
console.log(tmp)//未定义
}1
2
3
4
5
6if(true){
let tmp;
tmp = 'abc'
console.log(tmp)//abc
} - 上面的代码中,在let命令声明tmp之前,都属于变量tmp的”死区”。有些”死区”比较隐蔽,不太容易发现:
1
2
3
4function bar (x = y, y = 2){
return [x , y]
}
bar()//未定义 - 报错的原因是参数x默认等于参数y,而此时y还没有声明,属于”死区”。如果声明在使用之前就不会报错:
1
2
3
4function bar (y = 2 , x = y){
return [x , y]
}
bar()//2 2总结:暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已存在,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
细节注意1: 同一个块级作用域内,不允许重复声明同一个变量。
错误示范一:
1 | { |
错误示范二;
1 | { |
细节注意2:函数内不能用let重新声明函数的参数。
1 | function say(arg){ |
细节注意3: 块级作用域。
ES5只有全局作用域和函数作用域,没有块级作用域,这会导致以下几个不合理的场景:
- 第一种场景,内层变量可能会覆盖外层变量。函数f执行后,变量提升导致内层的tmp变量覆盖了外层的tmp变量。
1
2
3
4
5
6
7
8
9var tmp = new Data()
function f(){
console.log(tmp)
if(false){
var tmp = 'hello'
}
f()//undefined
} - 第二种场景,用来计数的循环变量泄露为全局变量。变量i只用来控制循环,但是在循环结束之后它并没有消失,而是泄露成为了全局变量。
1
2
3
4
5
6var s = 'hello'
for(var i = 0; i <s.length; i++){
console.log(s[i])// h e l l o
} - let实际上为JavaScript新增了块级作用域。
1
2
3
4
5
6
7
8
9function f1(){
let n = 5;
if(true){
let n = 10;
}
console.log(n)
}
f1()//5 - 上面函数有两个代码块,都声明了变量n,运行后输出5。表示外层代码块不受内层代码块的影响。
总结:用let声明变量只在块级作用域起作用,适合在for循环使用,也不会出现变量提升现象。同一个代码块内,不可重复声明的相同变量,不可重复声明函数内的参数。