es6相关

mac2022-06-30  133

前言:之前写了promise,generator,async,现在写一下es6的其他比较常用的地方。

1 es6变量

1.1 es5声明变量

声明变量的方式

var是js的一个关键字,用来声明变量的,在es5中,声明变量有两种方式: 第一种:var num=1。如果在方法中声明,则为局部变量;如果在全局中声明,则为全局变量; 第二种:num=1。事实上这是对属性进行赋值操作,首先它会首先尝试在当前作用域链(如果在方法中声明,则当前作用域代表全局作用域或方法局部作用域)中解析num,如果在任何作用域链中找到num,则会对num的属性进行赋值,如果没有找到num,他会在全局对象(即当前作用域链的最顶层对象,如window)中创造num属性并赋值。 注意!它并不是声明了一个全局变量,而是创建了一个全局对象的属性。 由于变量声明自带不可删除属性,比较var num=1跟num=1,前者是变量声明,带不可删除属性,因此无法被删除;后者为全局变量的一个属性,因此可以从全局变量中删除。

变量提升

js引擎的工作方式是,首先解析代码,获取所有被声明的变量,然后再一行一行的运行,这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升。 示例:

console.log(a); var a=1;

以上的语句不会报错,只是提示undefined。实际的运行过程:

var a; console.log(a); a=1;

表示变量a已声明,但还未赋值。但是变量提升只对var命令声明的变量有效,如果一个变量不是var变量声明的,就不会发生变量提升,例如以下实例:

console.log(aa); aa=1;以上代码将会报错aa is not defined

与普通变量一样,js中的function也可看做变量,也存在变量提升的情况:

a(); function a(){ console.log(1); }

表面上,上面的代码好像在声明之前就调用了函数a。但是实际上,由于变量提升,函数a定义部分被提升到了代码头部,也就是在调用之前已经声明了。但是,如果采用赋值语句定义函数,JavaScript就会报错:

a(); var a=function(){ console.log(1); }//会报 a is not a function

因为实际运行过程:

var a; a(); a=function(){ console.log(1); }//这时候a是个变量,并非function
栗子1
var a = 99; // 全局变量a f(); // f是函数,虽然定义在调用的后面,但是函数声明会提升到作用域的顶部。 console.log(a); // a=>99, 此时是全局变量的a function f() { console.log(a); // 当前的a变量是下面变量a声明提升后,默认值undefined var a = 10; console.log(a); // a => 10 } // 输出结果: undefined 10 99
栗子2
var a=12; function fn(){ var a=13; f1(); console.log(a); } function f1(){a=14;} fn(); console.log(a); //输出 //13 //14 var a=12; function fn(){ var a=13; f1(); console.log(a); } function f1(){ var a=14;} fn(); console.log(a); //输出 //13 //12
引申

以上两个例子若不能理解,可以看一下红宝书的js作用域和作用域链的相关问题,搞清楚变量的域链实际上是从创建这个变量的函数开始的,沿着作用域层层往上找,也可以参考这篇博客。 https://www.cnblogs.com/gaosirs/p/10579059.html

1.2 let

ES6 新增了let命令,用来声明局部变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效,而且有暂时性死区的约束。一般有以下几个特点:

块级作用域

使用let声明的变量,那么这个变量就有一个块级作用范围。块级作用域的含义:

独立的一对大括号,两个大括号之间就是变量a的块级作用域的范围。 { let a=1; } 条件语句,函数声明语句,循环语句等中的一对大括号内就是变量b,c,d的块级作用域的范围: if(true){ let b=2; }; function fangFa(){ let c = 3; }; while(true){ let d = 6; }; for循环语句中的一对小括号内中的设置循环变量那部分就是一种特殊的块级作用域: // 下面代码中 let i = 0 所在小括号就是变量i的特殊的块级作用域 // 这个特殊的块级作用域可以说时大括号这个作用域的父作用域 // 可以看成大括号内的作用域是嵌套在这个特殊作用域中的,也就是多个块级作用域的嵌套 for (let i = 0; i < 3; i++) { let i = 'abc'; console.log(i); }

打印三个abc。

注意:块级作用域在多层嵌套时作用域的范围(注意:因为有了使用let进行变量声明,所以才会有块级作用域的概念):

// 下面的代码中因为只有在第一个if语句中大括号中使用了let进行变量声明, // 所以整个大括号中的所有就是 变量a 的块级作用域 if(true){ let a = '123456'; if(true){ // 这里依然是变量a的块级作用域,所以在第二个if语句大括号中操作 变量 a 会产生效果 a = 'ABCDEF'; } // 控制台打印输出 ‘ABCDEF’ console.log(a); } // 下面的代码有三种块级作用域 // 第一种是变量a的块级作用域,因为在两个if语句大括号中都用let声明了变量a,所以第一个if语句中变量a的块级作用域范围是除了除了第二个if语句的所有范围,而第二个if语句中变量a的块级作用域范围就是第二个if语句的大括号内的所有 // 第二种是变量b的块级作用域,其作用域范围就是第二个if语句的大括号内的所有 // 第三种是变量c的块级作用域,因为在第二if语句中没有是使用let去声明变量c,所以变量c的块级作用域范围是第一个if语句中大括号中的所有。 // 块级作用域之间是互不干涉,相互独立的,所以下面的代码最后输出打印的是123456 // 因为互相独立,所以不同块级作用域中可以对同一个变量名进行重复声明 if(true){ let a = '123456'; let c = '欧几何'; if(true){ let a = 'ABCDEF'; let b = 'abcdef'; c = 'ojh'; } // 控制台打印输出 ‘123456’ console.log(a); // 因为块级作用域互不干涉,相互独立,所以这里是无法取到b变量的值,所以报错 console.log(b); // 控制台打印输出 ‘ojh’ console.log(c); }
暂时性死区

用let声明变量,不存在上文所说的变量提提升,但是要求必须 等let声明语句执行完之后,变量才能使用,不然会报Uncaught ReferenceError错误。

console.log(aicoder); // 错误:Uncaught ReferenceError ... let aicoder = 'aicoder.com'; // 这里就可以安全使用aicoder var tmp = 123; if (true) { // 变量tmp在这里使用了let进行声明,如此这里的大括号内都是变量tmp的块级作用域, // 变量在这个范围内就不在收外部影响了,即使范围外有同名的变量存在。 // 而且在 变量tmp 正式进行 声明赋值表达式 之前 就使用变量的话,是会报错的 // 这在语法上,声明赋值表达式 之前的区域称为“暂时性死区”(temporal dead zone,简称 TDZ)。 tmp = 'abc'; // ReferenceError let tmp;// 这是个声明赋值表达式,赋的是unfined值 }

注意: es6中明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,形成封闭作用域,在声明之前使用就会报错,在语法上称为“暂时性死区” (temporal dead zone,简称 TDZ)。

不能重复声明

let不允许在块级作用域内,重复声明一个变量。

function test(x){ let x = 1; } // test函数体中使用了 let 声明了 变量 x ,但是在参数中也有一个 x,在解释器执行 test() 方法时 // 会对参数 x 进行声明变量,此时就和 let 声明的 变量 x 由了冲突,从而报错。 test(); function test(x){ let a = 1; var a = 2; } // 解释器执行函数体时,同一个变量名重复进行声明了,如此会报错 test();

1.3 const

const的用法和let大部分都是相似的。但是const用于修饰常量,定义的变量不可修改,而且必须初始化。 const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。

但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。 下面是另一个例子。

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

上面代码中,常量a是一个数组,这个数组本身是可写的,但是如果将另一个数组赋值给a,就会报错。

如果真的想将对象冻结,应该使用Object.freeze方法。

const foo = Object.freeze({}); // 常规模式时,下面一行不起作用; // 严格模式时,该行会报错 foo.prop = 123;

2 es6箭头函数

2.1 使用方式

ES6允许 =>定义函数

var f = v => v; // 等同于 var f = function (v) { return v; };

箭头函数可以简化回调函数

// 正常函数写法 [1,2,3].map(function (x) { return x * x; }); // 箭头函数写法 [1,2,3].map(x => x * x);

但是使用箭头函数的几个注意点: (1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象 (2)不可以当做构造函数(不能使用new命令,否则报错) (3)不可以使用arguments对象,因为该对象在函数体内不存在,如果要用,可以yongrest (4)不可以使用yield命令,因此箭头函数不能用作Generator函数

2.1 箭头函数的this

上面四点中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的. 在es6中规定箭头函数this的定义为:箭头函数的this是在定义函数时绑定的,不是在执行过程中绑定的。简单的说,函数在定义时,this就继承了定义函数的对象。 所谓的定义时候绑定,就是this是继承自父执行上下文。箭头函数没有自己的this,继承的是它上一层的执行上下文。因为没有this也就不能用作构造函数。

箭头函数:

function foo(){ setTimeout(()=>{ console.log(“id”:this.id); },200); var id=21; foo.call({id:42;}); //42

普通函数:

function foo(){ setTimeout(function(){ console.log(this.id) }, 100); } var id=21; foo.call({id:42}); //21

上面代码中,setTimeout的参数是一个箭头函数,这个箭头函数的定义生效是在foo函数生成时,而它的真正执行要等到 100 毫秒后。如果是普通函数,执行时this应该指向全局对象window,这时应该输出21。但是,箭头函数导致this总是指向函数定义生效时所在的对象(本例是{id: 42}),所以输出的是42。 箭头函数可以让setTimeout里面的this,绑定定义时所在的作用域,而不是指向运行时所在的作用域。

function Timer() { this.s1 = 0; this.s2 = 0; // 箭头函数 setInterval(() => this.s1++, 1000); // 普通函数 setInterval(function () { this.s2++; }, 1000); } var timer = new Timer(); setTimeout(() => console.log('s1: ', timer.s1), 3100); setTimeout(() => console.log('s2: ', timer.s2), 3100); // s1: 3 // s2: 0

上面代码中,Timer函数内部设置了两个定时器,分别使用了箭头函数和普通函数。前者的this绑定定义时所在的作用域(即Timer函数),后者的this指向运行时所在的作用域(即全局对象)。所以,3100 毫秒之后,timer.s1被更新了 3 次,而timer.s2一次都没更新。 this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。

// ES6 function foo() { setTimeout(() => { console.log('id:', this.id); }, 100); } // ES5 function foo() { var _this = this; setTimeout(function () { console.log('id:', _this.id); }, 100); }

所以箭头函数不适用场合: (1)定义对象的方法的时候不适合使用箭头函数

const cat = { lives: 9, jumps: () => { this.lives--; } }

上面代码中,cat.jumps()方法是一个箭头函数,这是错误的。调用cat.jumps()时,如果是普通函数,该方法内部的this指向cat;如果写成上面那样的箭头函数,使得this指向全局对象,因此不会得到预期结果。这是因为对象不构成单独的作用域,导致jumps箭头函数定义时的作用域就是全局作用域。 (2)需要动态this的时候不适合使用箭头函数

var button = document.getElementById('press'); button.addEventListener('click', () => { this.classList.toggle('on'); });

上面代码运行时,点击按钮会报错,因为button的监听函数是一个箭头函数,导致里面的this就是全局对象。如果改成普通函数,this就会动态指向被点击的按钮对象。 (3)如果函数很复杂,内部有大量读写操作,不仅仅是为了计算值,这时也不应该使用箭头函数,而应该使用普通函数,这样可以提高代码的可读性。 箭头函数this总结:

window中定义的方法,es5和es6都一样指向window点击等事件处理中,es5指向绑定事件的dom元素,es6指向全局windowsetInterval定时器处理中,es5指向全局window,es6视情况不再指向全局。用bind,call,apply可以改变this的指向

参考文献

https://blog.csdn.net/xhf852963/article/details/79416440 cnblogs.com/fly_dragon/p/8669057.html https://blog.csdn.net/sinat_30552885/article/details/81532081 https://blog.csdn.net/weixin_37722222/article/details/81625826

最新回复(0)