ES6入门一:块级作用域(let&const)、spread展开、rest收集

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

二、const声明与块作用域

ES6中除了let可以实现块级作用域声明,还有const声明也可以实现块级作用域声明。const是用来声明常量的,也就是说声明必须有显式的初始化。如果需要undefined的常量,也是必须赋值undefined。

 1 //ES6声明常量
 2 {
 3     const a = 10;
 4     a = 20; //这里报错(看ES5中如何实现这种报错提示)
 5 }
 6 //ES5编译
 7 "use strict";
 8 function _readOnlyError(name) { throw new Error("\"" + name + "\" is read-only"); }
 9 {
10   var a = 10;
11   a = (_readOnlyError("a"), 20);
12 }

const声明常量的值不可以改变,是指不可以改变参数指向的栈内存的地址,如果时声明的常量时引用值类型的数据,可以改变对象的内部属性和方法。

 1 //ES6语法声明引用值类型常量
 2 {
 3     const obj = {
 4         a:10,
 5         b:function(){}
 6     }
 7     obj.a = [1,2,3];
 8     obj.b = 40;
 9     obj = {}
10 }
11 //ES5编译结果
12 "use strict";
13 function _readOnlyError(name) { throw new Error("\"" + name + "\" is read-only"); }
14
15 {
16   var obj = {
17     a: 10,
18     b: function b() {}
19   };
20   obj.a = [1, 2, 3];
21   obj.b = 40;
22   obj = (_readOnlyError("obj"), {});
23 }

最后,在这里添加一个与let和const无关的块级作用域问题,函数在独立的{}中也存在块级作用域概念(也可以说时严格模式下的函数表达式赋值),前面的if块级中起始已经有相关内容,函数的块级作用域只是作用在函数初始化在被执行执行时,在块级作用域内,函数的名称会以变量名的方式被提升到块级作用域外部,当块级作用域被执行过后才会真正挂载到外部的作用域对应的变量名称上,看下面这个编译结果就明白了:

 1 //ES6块级作用域下的函数
 2 {
 3     let a = 10;
 4     function fun(){console.log(a)}
 5 }
 6 fun();
 7 //ES5编译结果
 8 "use strict";
 9 {
10   var _fun = function _fun() {//_fun变量提升,执行时挂载函数
11     console.log(a);
12   };
13   var a = 10;
14 }
15 fun();

三、spread展开与rest收集

...展开&收集运算符

  • 写:function test(...arg){}; test(1,2,3)收集作用
  • 读:var arg = [1,2,3];console.log(...arg);展开作用

作用:简化书写长度,提升开发效率

ES6/ES7:ES6可以处理数组,ES7可以处理对象。

3.1应用展开替代apply:

 1 function foo(x,y,z){
 2     console.log(x,y,z);
 3 }
 4 foo(...[1,2,3]);
 5 //...的展开语法实际编译成ES5就是apply的应用,展开语法执行效率优于apply
 6 "use strict";
 7 function foo(x, y, z) {
 8   console.log(x, y, z);
 9 }
10 foo.apply(void 0, [1, 2, 3]);

3.2应用展开向数组指定位置合并元素(替代concat):

1 var a = [1,2,3];
2 var b = [0,...a,4,5];
3 //ES5的编译结果
4 "use strict";
5 var a = [1, 2, 3];
6 var b = [0].concat(a, [4, 5]);

3.3应用收集解决实参与形参不对称问题:

 1 function foo(a, b, ...c){
 2     console.log(a,b,c);
 3 }
 4 foo(1,2,3,4,5); //1 2 [3,4,5]
 5 //ES5的编译结果
 6 "use strict";
 7 function foo(a, b) {
 8   for (var _len = arguments.length, c = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
 9     c[_key - 2] = arguments[_key];
10   }
11   console.log(a, b, c);
12 }
13 foo(1, 2, 3, 4, 5); //1 2 [3,4,5]

3.4ES7中的...展开收集运算符实现对象属性浅层克隆:

 1 var obj1 = {
 2     a:"a",
 3     b:"b",
 4     c:[1,2,3]
 5 }
 6 var obj2 = {
 7     e:"e",
 8     f:{
 9         name:"F_NAME"
10     }
11 }
12 var obj = {
13     ...obj1,
14     ...obj2
15 }
16 console.log(obj); //{a: "a", b: "b", c: Array(3), e: "e", f: {…}}
17 obj.f.name = "f";
18 console.log(obj2.f.name); //f

如果需要实现深克隆的话可以将子级拆分出来,然后分别展开收集:

 1 var c = [1,2,3]
 2 var obj1 = {
 3     a:"a",
 4     b:"b",
 5     c:[...c]
 6 }
 7 var f = {name : "F_NAME"}
 8 var obj2 = {
 9     e:"e",
10     f:{
11         ...f
12     }
13 }
14 var obj = {
15     ...obj1,
16     ...obj2,
17     c:[...c],
18     f:{...f}
19 }
20 // console.log(obj);
21 obj.f.name = "f";
22 console.log(obj2.f.name); //F_NAME

其本质上就是采用了重新赋值的方式解决深克隆的需求。但是这种解决方案显然不适合层级较多的对象来实现,还可以使用JSON对象的方法来解决这类深克隆的问题:

 1 var obj1 = {
 2     a:"a",
 3     b:"b",
 4     c:[1,2,3]
 5 }
 6 var obj2 = {
 7     e:"e",
 8     f:{
 9         name:"F_NAME"
10     }
11 }
12 var obj3 = {
13     ...obj1,
14     ...obj2
15 }
16 var obj = JSON.parse(JSON.stringify(obj3))
17 console.log(obj);
18 obj.f.name = "f";
19 console.log(obj2.f.name); //F_NAME

采用JSON对象方法的这种深度克隆解决方案需要注意的是在方法中不能出现Function类型的属性,如果有这种情况,就只能采用递归的方式实现深度克隆了。

原文地址:https://www.cnblogs.com/ZheOneAndOnly/p/11332671.html

时间: 2024-08-24 15:41:59

ES6入门一:块级作用域(let&const)、spread展开、rest收集的相关文章

12.24 ES6浅谈--块级作用域,let

第一部分:ES6新增了块级作用域,let关键字用于声明变量,相较于var而言,let关键字不存在声明提前. 1.ES6真正的出现了块级作用域,使用双花括号括住并在其中用let声明变量,会存在暂时性死区,在作用域外面使用变量会报错. 2.在循环中,如for循环,let命令相较于var命令会更好,原因在于所声明的变量不会提前到全局.那么在循环结束以后再调用该变量将无法访问. 3.如果是使用var的for循环,如 var a = []; for (var i = 0; i < 10; i++) { a

es6中添加块级作用域的目的

原本只有函数作用域和全局作用域两种,这就导致出现很多不方便的地方: 1)for循环问题:在看js高程的时候,纠结在第七章好久,就是一个这样的实例 function createFunctions(){ var result = new Array(); for (var i = 0 ; i < 10 ; i ++){ result[i] = function (){ return i; } } return result; } 不管这段代码输出result[i]()中的i是几,结果都是10,这就

面试时面试官想要听到什么答案 关于es6中let、const、promise、块级作用域的问题

前言 前面写了一篇关于vue方面问题的面试题(面试时面试官想要听到什么答案(关于一些vue的问题)),感谢大家的阅读和意见,今天整理了一下我面试时经常会问到的一些关于es的问题,写了这篇文章,感谢拨冗翻阅拙作,敬请斧正.因为最近比较忙es6的问题就写了这些,写的比较水了,这些也是我比较常问的还有一些比较碎的内容面试问了但本文未体现只挑选了重点的,class和symbol也是两个重点,因为我了解不深所以就没有去问这两个 - -下面进入正文,本文会列举一些平时面试时问到的问题和答案,并说明我在当时问

ES6——块级作用域

前面的话 过去,javascript缺乏块级作用域,var声明时的声明提升.属性变量等行为让人困惑.ES6的新语法可以帮助我们更好地控制作用域.本文将详细介绍ES6新引入的块级作用域绑定机制.let和const声明机制及最佳实践 var声明 [变量提升] var声明会发生”变量提升“现象,即变量可以在声明之前使用,值为undefined function getValue(condition){ if(condition){ var value = 'blue'; return value; }

ES6里关于作用域的拓展:块级作用域

过去,javascript缺乏块级作用域,var声明时的声明提升.属性变量等行为让人困惑.ES6的新语法可以帮助我们更好地控制作用域. 一.var声明 1.变量提升:var声明会发生"变量提升"现象,即变量可以在声明之前使用,值为undefined function getValue(condition){ if(condition){ var value = 'blue'; return value; }else{ //此处可访问变量value,值为undefined return

bala001 浏览器中的JavaScript执行机制:09 | 块级作用域:var缺陷以及为什么要引入let和const?

前言:该篇说明:|请见 说明 —— 浏览器工作原理与实践 目录 在前面<07 | 变量提升:JavaScript 代码是按照顺序执行的吗?>这篇文章中,我们已经讲解了 JavaScript 中变量提升的相关内容,正是由于 JavaScript 存在变量提升这种特性,从而导致了很多于直觉不符的代码,这也是 JavaScript 的一个重要设计缺陷. 虽然 ECMAScript6(以下简称 ES6 )已经通过引入块级作用域并配合 let.const 关键字,来避开了这种设计缺陷,但是由于 Java

let和ES6块级作用域

es6中的块级作用域 之前在看360的培训课程时,知道了{...}是个块级作用域,错误的认为{...}中声明的函数变量都不会被外界访问到,在看了你不知道的JS之后,发现并不是这样的.在块级作用域中使用let声明的变量外界无法访问到. eg: var foo = true; if (foo) { let bar = foo * 2; bar = something( bar ); console.log( bar ); } c onsole.log( bar ); // ReferenceErro

ES6标准入门 第二章:块级作用域 以及 let和const命令

一.块级作用域 1.为什么需要块级作用域? ES5中只有全局作用域和函数作用域,带来很多不合理的场景. (1)内层变量可能会覆盖外层变量: var tem = new Date(); function f(){ console.log(tmp); if(false) { var tmp = "hello world"; } } f(); //undefined 变量提升导致了外层的内层的tmp变量覆盖了外层的tmp变量. (2)用来计数的循环变量泄露为全局变量: var s = &qu

ES6 之 let和const、块级作用域

let let 声明的变量只在其所在的代码块内有用 不存在变量提升 只要在会计作用域中存在let命令,它所声明的变量就绑定这个区域 不允许重复声明 ES6块级作用域 外层代码不受内存代码块的影响 ES6规定,在块级作用域之中,函数声明类似于let 允许在块级作用域内声明函数 函数声明会提升到所在块级作用域的头部 const const声明一个只读常量,一旦声明,常量的值就不能改变 只在声明所在的块级作用域内有效 const声明的常量与let命令相同,只在声明所在的块级作用域内有效 const不存