ES6相关知识学习
1. let和const关键字
1.1 let关键字
作用:与var类似,用于声明一个变量
特点 在块作用域内有效,不能重复声明,不会预处理,不存在变量提升
应用:循环遍历加监听
1 | <body> |
1.2 const关键字
作用:定义一个常量。
特点:不能被修改,其他特点和let一样,用于保存不变的数据。
1 | const KEY = "Hello"; |
2. 变量的解构赋值
- 从对象或数组中提取数据,并赋值给变量(多个)。
- 对象的解构赋值: let {n,a} = {n:’tom’,a: 12};
- 数组的解构赋值: let{a,b} = {1,’zhangsan’};
- 变量的解构赋值多用于给多个形参赋值。
1 | <script type="text/javascript"> |
3. 模板字符串
简化字符串的拼接,模板字符串必须使用包含,注意这个符号不是单引号,是tab键上面的那个键,变化的部分采用${xxx}来定义。
1 | <script type="text/javascript"> |
4. 对象的简写方式
省略同名的属性值,省略方法的function,
1 | <script type="text/javascript"> |
5. 箭头函数
- 箭头函数没有自己的this,箭头函数的this不是调用的时候决定的,而是在定义的时候处在的对象就是它的this。
- 箭头函数的this看外层是否有函数,如果有,外层函数的this就是内部箭头函数的 this,没有则this是window。
1 | <button id="btn1">测试按钮1</button> |
6. 三点运算符
1 | <script type="text/javascript"> |
输出结果为:
7. 形参默认值
1 | <script type="text/javascript"> |
输出结果为;
8. promise对象原理
Promise对象,代表了未来某个将要发生的事件(通常是一个异步操作);
有了Promise对象。我们可以将异步操作以同步的流程表达出来,避免了层层嵌套的回调函数,在ES6中,Promise是一个构造函数,用来生成promise实例。
8.1 使用步骤:
1 | <script type="text/javascript"> |
8.2 promise的三种状态
pending:初始化状态,fullfilled:成功状态,rejected:失败状态
9. Symbol属性
- ES6中添加一种原始的数据类型symbol(已经存在的原始数据类型有:string,number,boolean,null,underfined,对象)
- symbol属性值对应的值是唯一的,解决了命名冲突的问题。
- symbol值不能与其他数据进行计算,包括字符串拼接。
- for in,for of遍历时不会遍历symbol属性。
Symbol 值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中。
9.1 使用场景:
1 | <script type="text/javascript"> |
10. iterator接口机制
首先在JavaScript中,存在数组和对象的数据结构,ES6又增加了map和Set,这四种数据集合,当然还可以彼此使用,例如数组的成员是Map,Map的成员是对象。
iterator是一种接口机制,为各种不同的数据结构提供统一的访问机制。
为各种数据结构提供一种统一的,简单的访问接口。使得数据结构的成员能够按某种次序排列,在ES6中创造一种新的遍历方式for…of循环。
10.1 原理:
- 创建一个指针对象(遍历器对象),指向数据结构的起始位置。
- 第一次调用指针对象的next()方法,可以将指针指向数据结构的第一个成员。
- 第二次调用指针对象的next()方法,指针就指向数据结构的第二个成员。
- 不断的调用next()方法,直到它指向数据结构的结束位置。
10.2 模拟指针对象
1 | //模拟指针对象 |
- 每一次调用next()方法,都会返回数据结构的当前成员信息,具体来说,就是返回一个包含value和done两个属性的对象,其中,value表示当前成员的值,done属性是一个布尔值,遍历遍历是否结束。
- 一种数据结构只要部署了Iterator接口,我们就称这种数据结构是‘可遍历的(iterable)’。
- ES6规定,默认的Iterator接口部署在数据结构的Symbol.iterator属性,或者说一个数据结构只要只要有symbol.iterator属性,就可以认为是‘可遍历的’。
- symbol.iterator属性本身是一个函数,就是当前数据结构默认的遍历器生成函数,执行这个函数,就会返回一个遍历器
1 | <script type="text/javascript"> |
- 上述代码中,对象obj是可以遍历的,因为它具有Symbol.iterator属性,执行这个属性,就会返回一个遍历对象,该对象的根本特征就具有next方法,每一次调用next方法,都会返回一个代表当前成员的信息对象,具有value和done两个属性。
在ES6的有些数据结构原声就具备了Iterator接口(比如数组),即不用做任何的处理,就可以被for…of循环遍历,原因就在于这些数据结构原生就已经部署了Symbol.iterator属性,另外一些数据结构(比如对象)就没有部署Symbol.iterator属性,原生具备Iterator接口的数据结构如下:
- Array
- Map
- Set
- String
- TypedArray
- 函数的arguments对象
- NodeLise对象
10.3 可以使用for…of的例子
1 | // for...of的使用-数组 |
注意:使用三点运算符,解构赋值,默认会调用Iterator接口。
11. Generator函数
- Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不一样。
- Generator函数可以理解为一个状态机,封装了多个内部状态。
- 执行Generator函数会返回一个遍历器对象,也就是说,Generator函数除了一个状态机,还是一个遍历器生成函数。返回的遍历器对象可以一次遍历Generator函数内部的每一个状态。
- 形式上,Generator函数就是一个普通的函数,有两个特征,其一是:function关键字与函数名之间有一个星号,其二是:函数体内部使用yield表达式,定义不同的内部状态。
1 | function* myGenerator() { |
上述代码就定义了一个Generator函数myGenerator,在他的内部就有三个yield表达式,即函数就存在三个状态,但是,当我门调用函数的时候,函数并不会执行,返回的也不是函数的运行结果,而是一个指向内部状态的指针对象,也叫遍历器对象(iterator object)。
下一步就是调用遍历器的next方法,是的指针下移一个状态,也就是说,每一次的调用next方法,就遍历一个状态,
1 | <script type="text/javascript"> |
11.1 总结:
调用Generator函数返回遍历器对象,代表Generator函数的内部指针,以后每次调用遍历器对象的next方法,就会返回一个有着value和done属性的对象。value属性表示当前的内部状态值,是yield表达式后面的那个表达式的值。done属性是一个布尔值,表示是否遍历结束。
11.2 yield表达式
由于Generator函数返回的是一个遍历器对象,只有调用next方法才会遍历下一个内部状态,所以它其实提供的就是一种可以暂停执行的函数,yield表达式就是暂停的标志。
遍历器对象的next方法的运行逻辑如下:
- 遇到yield表达式,就暂停执行后面的操作,并将紧跟在后面yield后面的那个表达式的值,作为返回对象的value的属性值。
- 下一次调用next的方法时,再继续往下执行,直到遇到一个yield表达式。
- 如果没有遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回对象的value的属性值。
- 如果该函数没有return语句,则返回的对象的value属性值为undefined。
需要注意的是:yield表达式后面的表达式,只有当调用next方法,内部指针指向该语句才会执行。
1 | function* myGenerator1(){ |
如果Generator函数不使用yield表达式,就变成了一个单纯的暂缓执行函数。
1 | function* test(){ |
上述代码中test函数就是一个普通的函数,但它是一个Generator函数,就变成了只有调用next方法,才会执行text函数。
yield表达式只有在Generator函数中使用,其他地方使用就会报错。如果,yield表达式使用在另一个表达式中,就必须放在圆括号里面,如下所示
1 | function* demo(){ |
11.3 next方法的参数
yield表达式本身没有返回值,或者说总是返回undefined,next方法可以带一个参数,该参数就会被当成上一个yield表达式的返回值。
1 | function* foo(x){ |
上述代码中,第一次调用next方法,返回x+1的值6,第二次调用调用next方法,next方法中传入参数为12,就说上一次yield表达式的值为12,因此y等于24,返回的是y / 3的值8,第三次调用next方法,传入参数13,因此 z 等于13,这个时候x等于5,y等于24,z等于13,返回42。
11.4 for of遍历Generator函数
下面是一个利用 Generator 函数和for…of循环,实现斐波那契数列的例子。
1 | function* fibonacci(){ |
11.5 Generator函数的小练习
1 | // 需求就是先一部发送发送1号请求,根据1号请求返回的数据中的comurl值作为2号请求的url |
分析:执行到1语句的时候,就会调用show方法中的第一个yield,然后执行getDept方法,在将返回的url做为next的参数,在调用show方法中的第二个yield,再执行getDept方法,发送第二次请求。
1 | // 需求就是先一部发送发送1号请求,根据1号请求返回的数据中的comurl值作为2号请求的url |
11.6 遍历对象调用return方法,
遍历对象调用return方法后,返回的值的value就是return方法的参数值,并且 Generator函数的遍历就终止了,返回的done的值为true。以后调用next方法,done的属性值就一直是true,例如
1 | function* hello(){ |
如果return方法调用时,不提供参数,则返回值的value属性为undefined。
11.7 yield*表达式
1 | function* foo(){ |
上述代码存在Generator函数中调用Generator函数,总输出结果我们可以看出是不会有效果的,这个时候就需要使用yield*表达式,如下
1 | function* foo(){ |
12. class的基本语法
1 | class Point { |
- 上述代码中,定义了一个类,可以看到里面有一个构造方法,其中this关键字就表示实例对象,除了一个构造方法外,还有一个toString方法,注意,定义‘类’的方法的时候,不需要加上function关键字,直接将函数定义放进去就可以,另外,方法之间也不需要逗号隔开。使用的时候,采用new命令。和构造函数的用法一样。
- 由于类的方法都是定义在原型prototype对象上,所以类的新方法可以添加在prototype对象上面,Object.assign方法可以很方便的一次向类中添加多个方法。
1 | class Point1{ |
在ES6的严格模式下:constructor方法是类的默认方法,通过new命令生成对象实例,自动调用该方法,一个类就必须有constructor方法,如果没有显示定义,一个空的constructor方法就会被默认添加。类必须通过new调用。否则就会报错。
12.1 class表达式
与函数一样,类可以使用表达式的形式定义
1 | const MyClass = class Me{ |
上述代码就是使用表达式定义了一个类,需要注意的是,这个类的名字是MyClass,而不是Me,Me只在class内部代码可用,指向当前类。
12.2 this指向
类的方法内部如果含有this,它默认指向类的实例,但是,一旦单独使用,就很可能会报错。
1 | class Logger { |
上述代码中,printName方法中的this,默认指向的是Logger类的实例,但是,如果将这个方法提取出来再外部使用的话,this就会指向该方法运行时所在的环境,因为找不到print方法而导致报错。
解决办法之一:构造方法中绑定this
1 | class Logger { |
解决办法之二:使用箭头函数
1 | class Logger { |
12.3 name属性
每一个类都具有name属性,name属性总是返回紧跟在class关键字后面的类名
1 | class Beauty{} |
12.4 Class的静态方法
在一个类中定义的方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来直接调用,这个方法就叫静态方法。
1 | class Foo{ |
注意:如果静态方法包含this关键字,这个this指向的是类,而不是类的实例。
1 | class Foo{ |
- 从上述代码可以看出。静态方法getName中调用了this.getAge(),这里的this指的是Foo类,而不是Foo的实例。等同于调用了Foo.getAge(),
- 还可以看出静态方法和非静态方法可以重名。
- 父类的静态方法,可以被子类继承。
1 | class Sun extends Foo{} |
静态方法也是可以从supper对象上调用的。
1 | class Fa{ |
13. class的继承
13.1 简介
class可以通过extends关键字实现继承。
1 | class Point{} |
注意的是:子类必须再constructor方法中调用super方法,否则砸新建实例的时候就会报错,这是因为子类没有自己的this对象,而是继承父类的this对象,如果不调用super方法,子类就得不到this对象。
13.2 Object.getPrototypeOf()
Object.getPrototypeOf方法可以用来从子类上来获取父类,由此判定一个类是否继承了另一个类。
1 | let isExtends = Object.getPrototypeOf(ColorPoint); |
14. Set和Map数组
14.1 Set
基本用法
ES6几桶了一种新的数据结构Set,他类似数组,但是成员的值都是唯一的,没有重复的值,Set本身是一个构造函数,用来生成Set数据结构。
1 | const s = new Set(); |
上述代码就可以看出通过add方法向Set结构中加入成员,结果表明Set结构不会添加重复的值。
Set函数可以接受一个数组(或者具有iterable接口的其他数据 结构)作为参数 ,用来初始化
1 | const set = new Set([1,2,3,4,4]); |
上述代码也说明了一种去除数组重复成员的方法1
[...new Set(array)]
向Set加入值的时候,不会发生类型转换,也就是说,a和’a’是两个不同的值。另外,对象总是不相等的,
1 | let obj = {}; |
上述代码说明两个空对象是不相等的,如果传入同一个new出来的对象实例,那么就会被视为相同。
Set实例的属性和方法
Set结构的实例有以下属性
- Set.prototype.constructor:构造函数,默认就是Set函数。
- Set.prototype.size:返回Set的实例的成员总数。
Set实例的方法分为操作方法(用于操作数据)和遍历方法(用于遍历成员)
- add(value):添加某一个值,返回Set结构本身。
- delete(value):删除某一个值,返回一个布尔值,表示是否删除成功。
- has(value):返回一个布尔值,表示该值是否为Set的成员。
- clear():清除所有成员,没有返回值。
1 | let setMethod = new Set(); |
Array.from方法可以将Set结构转为数组
1 | const itemsSet = new Set([1,2,3,4,5]); |
数组去重的一种方法
1 | function dedupe (array){ |
遍历操作
Set结构的实例有四个遍历方法,用于遍历成员
- key():返回键名的遍历器
- values():返回键值的遍历器
- entries():返回键值对的遍历器
- forEach():使用回调函数遍历每一个成员
说明:Set的遍历顺序就是插入顺序。
1 | let setTest = new Set(['张三','李思思','刘亦菲']); |
由于Set结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法一样。
Set结构的实例默认可以遍历,他的默认遍历器函数就是他的values方法,意味着遍历时可以省略values()方法。
1 | let setSim = new Set(['red','green','blue']); |
forEach()
Set结构和数组一样,也拥有forEach方法,用于对每个成员执行某种操作,没有返回值。
1 | setSim.forEach((value,key)=>console.log(key,value)) |
遍历的应用
扩展运算符(…)内部使用for of 循环,所以也可以用于Set结构
1 | let arr = [...setSim]; |
扩展运算符和Set结合就可以去除数组中的重复成员
1 | let setArr = [3,54,1,5,4,1,5,4,8]; |
而且数组的map和filter方法也可以间接用于Set。
1 | let setMap = new Set([1,2,3]); |
因此,可以使用Set可以很容易的实现并集,交集和差集。
1 | let a = new Set([1,2,3]); |
WeakSet
WeakSet结构与Set类似,也是不重复的值的集合,但是,它与Set的不同之处在于WeakSet的成员只能是对象,而不能是其他类型的值。
14.2 Map
含义和基本用法
JavaScript的对象(Object),本质上市键值对的集合(Hash结构),但是传统上只能用于字符串当键,这给他的使用带来限制。因此在ES6中,提出了Map的数据结构,他类似对象。也是键值对的集合。但是,键的范围不限制与字符串,各种类型的值(包括对象)都可以当做键,也就是说,Object结构提供了‘字符串-值’的对应,Map提供了‘值-值’的对应。
1 | const m = new Map(); |
上述代码使用了Map结构的set方法,将对象obj作为m的一个键,然后使用get方法读取obj键的值,使用has方法判断是否灿在obj这个键,使用delete删除obj这个键。展示了如何向一个Map中添加成员,作为构造函数,Map也接受一个数组作为参数,该数组的成员是一个表示键值对的数组。
1 | const map = new Map([ |
上面代码在新建Map实例时,就指定了两个键name和title。
如果对同一个键进行多次赋值,后面的值将会覆盖前面的值。
1 | const mapTest = new Map(); |
注意:只有对同一个对象的引用,Map结构才将其视为同一个键。
1 | const unmap = new Map(); |
上述代码的set和get方法,表面是针对同一个键,但实际上是这两个值的内存地址不一样,因此get方法无法取到该键。
同理,同样的值的两个实例,在Map结构中被视为两个键。
1 | const map1 = new Map(); |
上述代码中变量k1和k2的值是一样的,但是在Map中他们是两个不同的键。由此可知,
- Map的键实际和内存地址绑定的,只要内存地址不一样,就视为两个键。
- 如果Map的键是一个简单类型的值(数字,字符串,布尔值),则只要这两个值严格相等,Map将其视为一个键,比如0和-0就是一个键,布尔值true和字符串true就是两个不同的键,
- 另外,undefined和null也是两个不同的键,
1 | let map2 = new Map(); |
实例的属性和操作方法
- size属性:返回map结构的成员总数。
- set(key,value):设置键名和对应的键值,然后返回整个Map结构,如果key已经存在值,则键值将会更新,否则生成新的键。由于set方法返回的是当前map对象,所以可以采用链式写法。
- get(key):读取对应key的值,如果找不到key,就返回undefined。
- has(key):返回一个布尔值,表示某一个键是否存在当前的Map对象中。
- delete(key):删除一个键,返回删除结果,成功返回true,否则,返回false。
- clear():清除所有成员,没有返回值
遍历方法
Map结构原生提供了三个遍历器生成函数和一个遍历方法
- keys():返回所有的键名的遍历器。
- values():返回键值的遍历器。
- entries():返回所有成员的遍历器。
- forEach():遍历Map的所有成员
需要注意的是:map的遍历顺序就是插入顺序。
1 | const map3 = new Map([ |
map结构转为数组结构,比较快速的办法就是使用扩展运算符(…)
1 | const map4 = new Map([ |
结合数组的map方法,filter方法,可以实现Map的遍历和过滤,(Map本身并没有map和filter方法)
1 | const map5 = new Map(); |
此外,map还有一个forEach方法,与数组类似,实现遍历。
1 | map5.forEach((v,k,m)=>console.log(`key:${k},value:${v}`)); |
与其他数据结构互相转换
- Map转数组:使用扩展运算符
- 数组转Map:将数组传入Map构造函数中,就可以转为Map
- Map转对象:如果所有的Map的键都是字符串,他就可以转为对象
1 | function maptoObject(map) { |
对象转Map
1
2
3
4
5
6
7
8
9function objtoMap(obj){
let map = new Map();
for(let k of Object.keys(obj)){
map.set(k,obj[k]);
}
return map;
}
let obj1 = {yes:true,no:false};
console.log(objtoMap(obj1));// Map(2) {"yes" => true, "no" => false}Map转为JSON
Map转为JSON要区分两种情况,一种情况是:Map的键名都是字符串,这时可以选择转为对象JSON。先将Map转为对象,在将对象转为JSON
1 | function maptoObject(map) { |
还有一种情况就是:Map的键有非字符串,这时候可以选择转为数组JSON
1 | function maptoArrJson(map){ |
15 数组的扩展
15.1 扩展运算符
扩展运算符在前面也已经介绍过了,也叫三点运算符,将一个数组转为用逗号分隔的参数序列。
1 | console.log(...[1,2,3]); |
替代函数的apply方法
由于扩展运算符可以展开数组,所以就不需要apply方法,
1 | //ES5的写法 |
运用扩展运算符实现的一个小例子,求一个数组中最大的元素
1 | // ES5的写法 |
let max2 = Math.max(…[14,85,95]);
console.log(max2);
使用push方法,将一个数组添加到另一个数组的尾部
1 | //ES5的写法 |
扩展运算符的应用
复制数组
数组是复合的数据类型,直接复制的话,只是复制了指向底层数据结构的指针,而不是克隆了一个全新的数组。1
2
3
4const a1 = [1,2];
const a2 = a1;
a2[0] = 3;
console.log(a1[0]);// 3
上述代码中,a2并不是a1的克隆,而是指向同一份数据的另一个指针,修改a2,回直接导致a1的变化,
1 | const a3 = [1,2]; |
使用扩展运算符,就可以很方便的实现数组的克隆,这个时候你修改a4或者a5的值,不会影响a3的值。
合并数组
使用扩展运算符可以很方便的实现数组的合并
1 | //ES5的写法 |
与解构赋值结合
扩展运算符与解构赋值相结合,可以生成数组
1 | const [first,...end] = [1,2,3,4,5]; |
如果将扩展运算符用于数组赋值,只能放在参数的末尾,否则就会报错。
1 | const [...test,last] = [1,2,3,4,5];// 报错 |
扩展运算符将字符串转为真正的数组
[...'hello']
总结:扩展运算符内部调用的是数据结构的Iterator接口,因此只有具有Iterator接口的对象,才能使用扩展运算符,否则就不能使用。
Array.from()
Array.from()方法可以将两类对象转为真正的数组
1 | let arrayLike = { |
Array.of()
Array.of方法用于将一组值,转换为数组,是为了弥补Array构造函数的不足
1 | console.log(Array());// [] |
上述代码中Array的参数个数有差异,说明只有参数个数大于1才能生成新的数组,of方法就是解决这个问题的。
1 | console.log(Array.of(3));// [3] |
Array.of()方法基本可以代替Array()或者new Array(),该方法总是返回一个新的数组,如果没有参数就会返回一个空的数组。
数组实例copyWithin()
数组实例的copyWithin()方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组,也就是说,使用这个方法,会修改当前数组。
1 | Array.prototype.copyWithin(target,start = 0,end = this.length) |
该方法的参数说明
- target(必须):从该位置开始替换数据,如果为负值,表示倒数。
- start(可选):从该位置开始读取数据,默认为0,如果为负值,表示倒数。
- end(可选):到该位置前停止读取数据,默认是数组的长度,如果为负值,表示倒数。
这三个参数都应该是数值,如果不是,会自动转为数值。
1 | let carr = [4,8,6,7,9,2].copyWithin(0,3); |
上述代码表示:从3号为位开始复制,替换0位以后的数据。
数组实例的find()方法和findIndex()方法
数组实例的find方法用于找到第一个符合条件的数组的成员,它的参数是一个回调函数,所有的数组成员都将依次执行这个函数
1 | [8,4,5,7,101].find((n) => n >100); |
数组实例的fill()
fill方法使用给定值,填充一个数组。1
2let farr = ['a','b','c'].fill(7);
console.log(farr);// [7, 7, 7]
fill方法用于空数组的初始化就很方便,该方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。
数组实例的include()方法
该方法返回一个布尔值,表示每一个数组是否包含给定的值,与字符串的include方法类似,该方法的第二个参数表示搜索的起始位置。如果为负数,表示倒数。
ES6的相关知识就学习结束了,感谢阮一峰出版的《ECMAScript 6 入门》书籍。值得购买,作为前端人们书籍还是不错的。