• let声明
  • const声明
  • 块级作用域
  • spread/rest

 一、let声明与块作用域

在ES6之前,JavaScript中的作用域基本单元就是function。现在有了let就可以创建任意块的声明,也被称为作用域块。这意味者只需要“{}”就可以创建一个作用域。不再像var声明变量那样总归属于包含函数或全局。例如:

1 var a = 2;
2 {
3     let a = 3
4     console.log(a); //3
5 }
6 console.log(a); //2

在相关的资料中看到有介绍,let声明在被正式列入标准之前长这样:

1 //这种写法不合法,可以理解它为let语法的前身
2 let (a = 2, b, c){
3     //...
4 }

这种写法从语法设计角度来说被称为显示声明,也就是说声明一个作用域块,而现在{let a = 1;}这种写法被称为隐式声明,这种声明被称为作用域劫持。我想选择隐式的声明方式不是没有原因的,比如let在for中的应用:

1 var func = [];
2 for(let a = 0; a < 5; a++){
3     func.push(function(){
4         console.log(a);
5     });
6 }
7 func[3](); //3 (如果for内使用var声明a,打印结果会是5)

通过上面for的示例可以看到let在每次循环时都会劫持一个作用域,每次都会有一个独立的a变量。而不是像以前var会把变量提升到for所在的作用域,循环内使用公共的变量。这种块级作用域如果采用“let(){}”显示声明好像就无法处理了,当然形式不是重点,而是需要说明let会将它所在的块作为独立的一层作用域。但是在if条件语句中let的作用域劫持依然成立,但是if条件语句内产生的块级作用域内部出现的方法,会被if条件所在的作用域引用:

 1 let something  ;//  true | false 
 2 if(something){
 3     let a = 10;
 4     function foo(){
 5         console.log(a);
 6     }
 7 }else{
 8     let a = 20;
 9     function foo(){
10         console.log(a);
11     }
12 }
13 foo(); //这里会打印 10 或者 20
14 console.log(a); //这里报错,说明let会劫持条件语句的块,但foo执行正确只能说明foo的被if条件所在的作用域引用

剩下的while和do...while这些块都可以被let劫持,于for循环没有差异,就不逐个展示了。但是,还有一个重要的内容需要注意,有了作用域就必然会有作用域提升编译相关的底层逻辑值得关注。

let块及作用域的编译执行逻辑:

{
    console.log(b); //报错:无法在初始化之前访问'b'
    let b = 20;
}

这个异常提示说明let声明依然会提升,但是在没有赋值的情况下不会像var那样返回默认值undefined,而是不能访问。也就是说,let声明的变量必须在赋值后使用,没有默认值undefined。

通常也把这种异常称为“临时死区”。然后,到这里让我想到了上一篇的严格模式,如果在let劫持的作用域中出现了不严格的代码书写,会产生什么样的编译逻辑呢?

1 console.log(a); //a is not defined
2 {
3     console.log(a); //a is not defined
4     a = 10;
5     let b = 20;
6     console.log(a); // 10
7 }
8 console.log(a); // 10

显然,在let劫持的作用域下出现了未声明的变量赋值,会被默认处理成var声明,但这要发生在赋值时,这个原因是let虽然劫持作用域但是依然保持作用域链的特性,对当前作用域找不到的变量会向上层作用域遍历,直到全局作用域,如果没有找到还是会在全局作用域声明赋值。

最后还有一个Function在块级作用域中的编译:

1 {
2     fun(); //20
3     let a = 10;
4     function fun(){
5         console.log(20); //在这段代码中这里不能是console.log(a),不然第二行代码会在执行时报错
6     }
7 }

在这个块级代码中的方法fun执行说明了方法在块级作用域中依然会被提升。但是需要注意,如果在fun中如果时打印a的值,以上的代码就会报错,因为出现了临时死区。除了以上的语法和编译值得我们探究,想更深入的了解let的实现原理,可以对比ES5的编译代码:

let声明的底层实现

let本质上起始就是块级作用域和var声明的严格模式,这里用上面if条件的示例来说明:

 1 //ES6语法
 2 {
 3     let something = true;
 4     if(something){
 5         let a = 10;
 6         function foo(){
 7             console.log(a);
 8         }
 9     }else{
10         let a = 20;
11         function foo(){
12             console.log(a);
13         }
14     }
15     foo();
16 }
17 //ES5编译结果
18 "use strict";
19 {
20   var something = true;
21 
22   if (something) {
23     var _foo = function _foo() {
24       console.log(a);
25     };
26 
27     var a = 10;
28   } else {
29     var _foo2 = function _foo2() {
30       console.log(_a);
31     };
32 
33     var _a = 20;
34   }
35 
36   foo();
37 }

这里可以可以思考以下for循环的let作用域劫持怎么编译成ES5的代码(还是使用前面for的示例):

 1 //ES6的语法
 2 var func = [];
 3 for(let a = 0; a < 5; a++){
 4     func.push(function(){
 5         console.log(a);
 6     });
 7 }
 8 func[3]();
 9 //ES5的编译结果
10 "use strict";
11 
12 var func = [];
13 
14 var _loop = function _loop(a) {
15   func.push(function () {
16     console.log(a);
17   });
18 };
19 
20 for (var a = 0; a < 5; a++) {
21   _loop(a);
22 }
23 
24 func[3]();
View Code

相关文章:

  • 2021-12-05
  • 2021-10-19
  • 2021-04-09
  • 2022-12-23
  • 2021-08-08
  • 2022-12-23
  • 2019-10-21
  • 2021-10-20
猜你喜欢
  • 2022-12-23
  • 2021-09-22
  • 2022-12-23
  • 2022-01-30
  • 2018-02-20
  • 2022-12-23
  • 2021-08-15
相关资源
相似解决方案