认识一下let和const

let

let不存在变量提升

老规矩,先看代码

 1 console.log(a) //undefined
 2 let lock = false
 3 
 4 if (lock) {
 5     var a = 10
 6 }else{
 7     var a =20
 8 }
 9 
10 console.log(a) //20

ES5中只有全局作用域和函数作用域,因此var变量声明被提升到了全局作用域,ES6有了块级作用域,let 声明的变量只能在声明的作用域以及其子作用域使用,而var依然只有全局和函数作用域之分。再看下面代码。

1 console.log(a) // 报错:a is not undefined
2 let lock = false
3 
4 if (lock) {
5     let a = 10
6 }else{
7     let a =20
8 }

a定义在if的block scope(块级作用域)里,let不存在变量提升,全局作用域无法使用

注意下面这种写法会报错

let lock = false

if (lock)  let a = 10  //报错: Lexical declaration cannot appear in a single-statement context
else let a =20


console.log(a)  

  

暂存死区(TDZ)

look代码

1 {
2     console.log(b)  //报错:Cannot access 'b' before initialization
3     typeof(b) //报错同上
4     let b
5 }

在let b所属的作用域内,从作用域顶部到let b声明语句之前都存在暂存死区,此时对b进行任何有关的操作均会报错,这就是暂存死区。在全局作用域和函数作用域,let声明的变量都存在暂存死区现象。

const声明的变量也存在暂存死区。

注意事项

同一作用域内let声明的变量不允许重复声明

1 {    
2     let b 
3     let b  //报错:Identifier 'b' has already been declared
4 }

外层作用域无法访问内层作用域中let声明的变量,但内层可以访问到外层let声明变量。内外层声明互不干扰。自己可以动手谢谢看。

常见例子

 1 for (var i = 0; i < 5; i++) {
 2     setTimeout(function() {
 3         console.log(i);
 4     }, 1000);
 5 }
 6 //这个例子执行完输出什么结果?
 7 
 8 //答案是 5,5,5,5,5
 9 
10 //如果想要输出0,1,2,3,4改怎么实现?
11 //其中一种方案就是把var换成let就可以了。let的块级作用域在这里就体现出来了。
12 
13 for (let i = 0; i < 5; i++) {
14     setTimeout(function() {
15         console.log(i);
16     }, 1000);
17 }

为什么下面的代码可以实现需求所需要的输出?

这就需要用到函数预编译相关知识了。函数的预编译在函数执行前一刻才会进行。这时候函数会把参数传入函数内部。对上面含var的for循环而言,当函数调用时也就是1s后,这时for已经执行完,函数预编译传入参数i。var声明的变量没有块级作用域概念,  仅有全局和函数作用域,故函数预编译寻找参数i的值时,找到的都是在全局作用域中的i,即都为5。

对含let的for循环,循环每进行一次就声明一个i,这个i仅在那次循环的块作用域中,因此当函数执行时,预编译找到的参数i值仍是当时那次循环体内的对应的那个i值。

在ES6以前,还有一种方法可以实现这种需求。

1 for (var i = 0; i < 5; i++) {
2        (function (i) {
3            setTimeout(function () {
4                console.log(i);
5            }, 1000);
6        })(i);
7    }

这段代码和let那段for循环都可以实现一样的输出,为什么?

前面说了,函数预编译在函数执行前一刻进行,这里使用立即执行函数(IIFE),当函数定义完立即执行,此时循环还未执行完,i就已经被当作参数传入外面那层function,当一秒后内存function执行时,函数预编译在上一层函数作用域(和var的全局作用域找到的i不同)找到了那一次循环时的i值,因此效果和let一样,但原理上并不完全相同。

参考babel对ES6中let转换成ES5的方法也印证了我上面的说法。

关于函数预编译和立即执行函数相关知识,可参考下面两个链接。

https://zhuanlan.zhihu.com/p/50236805

https://www.cnblogs.com/L-xmin/p/11178599.html

说完了let,说说const吧

let具有的特征const都具有,比如不可重复声明和暂存死区等。下面说说const特有的

const声明时必须赋值,且该值只读,不可修改。emmm,对object和array有点不一样。

对于复合类型的变量,变量名不指向数据,而是指向数据所在的地址。const命令只是保证变量名指向的地址不变,并不保证该地址的数据不变,所以将一个对象声明为常量必须非常小心。

1 const a = [];
2 a.push('Hello'); // 可执行 
3 a.length = 0; // 可执行 
4 a = ['Dave']; // 报错

上面代码中,常量foo储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。

1 const a = [];
2 a.push('Hello'); // 可执行 
3 a.length = 0; // 可执行 
4 a = ['Dave']; // 报错

如果要做到对象完全不可更改,可以使用Object.freeze(),参考:https://segmentfault.com/a/1190000019348510

还有点破事。。。

前面讲var的时候博主讲过,对变量提升一词并没有准确的定义,而现在流行的看法也不见得完美无缺。

众所周知let不存在变量提升,事实真的是这样吗?来看看下面这张图。

注意在图1中,代码执行到var i = 0时,c的值在chrome的监控中是not available。但执行到块作用域内的var a = 20时候,c为undefined。如果按照之前var的说法,变量提升是把变量声明提升到作用域顶部,

并初始化为undefined,那在这里的块作用域let是不是也存在所谓的变量提升?然let与var不同,即便c为undefined,但你在let c语句前对c的任何操作浏览器均会报错,因为暂存死区。如果按照另一种说法,变量提升的判断标志是能否在声明前使用,那么let是不存在变量提升的。注意,上面的情况只有块作用域有,全局作用域的let没有这种现象。这也许与chrome内部的机制有关,由于博主能力有限,并无法深究下去,有想法的小伙伴可以给我私信哦。但这不影响我们使用let,所以,以后尽量使用let和const声明变量而不是var。

 

猜你喜欢

转载自www.cnblogs.com/AwenJS/p/12390377.html
今日推荐