- 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]();