三、数组的扩展
1、Array.from
用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iteratble)对象
包含ES6新增的数据结构Set和Map
//ES6的写法
let arr2 = Array.from(arrayLike); //['a','b','c']
只要是部署了iterator接口的数据结构,Array.from都能将其转为数组
Array.from('hello'); //将字符串转为数组['h','e','l','l','o']
let nameSet = new Set(['a','b','c'])
Array.from(nameSet); //['a','b']
2、Array.of()
用于将一组值转换为数组,这个的主要目的是弥补数组构造函数Array()的不足
Array.of(3,18,8); //[3,18,8]
3、数组实例的find()和findIndex()
find()方法用于找出第一个符合条件的数组成员
它的参数是一个回调函数,所有数组成员依次执行该回调函数,知道找出第一个返回值为true的成员,然后返回该成员
如果没有符合条件的成员,则返回undefined
find()方法的回调函数可以接受三个参数,依次为当前的值,当前的位置和原数组
[1,4,-5,10].find((n)=>n<0); //-5
findIndex()方法与find()方法非常类似,更换第一个符合条件的数组成员的位置
如果所有成员都不符合条件,则返回-1
[1,5,10,15].findIndex(function(value,index,arr){
return value > 9;
}); //2
4、数组实例的fill()
fill()方法使用给定的值填充一个数组
['a','b','c'].fill(7); //[7,7,7]
new Array(3).fill(7); //[7,7,7]
5、数组实例的entries(),keys()
这两个方法用于遍历数组,它们都返回一个遍历器对象,可以用for...of循环进行遍历
唯一的区别是:keys()是对键名的遍历,entries()是对键值对的遍历
for(let index of ['a','b'].keys()){
console.log(index)
}
for(let [index,elem] of ['a','b'].entries()){
console.log(index,elem)
}
6、数组实例的includes()
该方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似。ES6引入了该方法
[1,2,3].includes(2); //true
[1,2,3].includes(4); //false
[1,2,NaN].includes(NaN); //true
第四章 Set和Map数据结构及Promise
一、Set
1、set实例的创建
它类似于数组,但是成员的值都是唯一的,没有重复的值
set本身是一个构造函数,用来生成set数据结构
const s = new Set();
[2,3,4,5,3,2,2].forEach(x => s.add(x));
console.log(s); //2,3,4,5
Set函数可以接收一个数组(具有iterator接口的其他数据结构)作为参数,用来初始化
[...new Set(array)] //去除数组的重复成员
2、set实例的属性和方法
Set结构的实例有以下属性:
1)Set.prototype.constructor:构造函数,默认就是Set函数
2)Set.prototype.size:返回Set实例的成员总数
Set结构实例有以下方法:
1)add(value):添加某个值,返回Set结果本身
2)delete(value):删除某个值,返回一个布尔值,表示删除是否成功
3)has(value):返回一个布尔值,表示该值是否为Set成员
4)clear():清楚所有成员,没有返回值
5)keys():返回键名的遍历器
6)values():返回键值的遍历器
7)entries():返回键值对的遍历器
8)forEach():使用回调函数遍历每个成员
二、Map
1、Map实例的属性和方法
Map类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各类型的值(包括对象)都可以当做键
也就是说:Object结构提供了“字符串--值”的对应,Map结构提供了“值--值”的对应,是一种更完善的Hash结构的实现
如果你需要“键值对”的数据结构,Map比Object更合适
Map可以接受一个数组作为参数,该数组的成员是一个个表示键值对的数组
const map = new Map([[name,'张三'],['title','Author']]);
Map结构的实例有以下属性:
Set.prototype.size:返回Map实例的成员总数
Map结构的实例有以下方法:
1)set(key,value):set方法设置键名可以对应的键值为value,然后返回整个Map结构
如果key已经有值,则键值会被更新,否则就新生成该键
2)get(key):get方法读取对应的键值,如果找不到key,返回false
3)has(key):has方法返回一个布尔值,表示某个键是否在当前Map对象之中
4)delete(key):delete方法删除某个键,返回true。如果删除失败,返回false
5)clear():清除所有成员,没有返回值
6)keys():返回键名的遍历器
7)values():返回键值的遍历器
8)entries():返回键值对的遍历器
9)forEach():使用回调函数遍历每个成员
三、Iterator
1、Iterator(遍历器)的概念
遍历器(iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制
任何数据结构只要部署iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)
iterator的作用有三个
1)为各种数据结构提供一个统一的、简便的访问接口
2)使得数据结构的成员能够按某种次序排列
3)ES6创造了一种新的遍历命令for...of循环,iterator接口主要提供for...of消费
iterator的遍历过程是这样的:
1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上是一个指针对象
2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员
3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员
4)不断调用指针对象的next方法,直到它指向数据结构的结束位置
2、默认iterator接口
iterator接口的目的:是为所有的数据结构提供了一种统一的访问机制,即for...of循环
当使用for...of循环遍历某舟数据结构的时候,该循环会自动去寻找iterator接口
一种数据结构只要部署了iterator接口,我们就称这种数据结构事“可遍历的”
可以通过以下方法访问iterator对象:
var iterator = iterObj[Symbol,iterator]();
原生具备iterator接口的数据结构如下:
Array
Map
Set
String
TypeArray
函数的arguments对象
NodeList对象
四、Promise
1、promise介绍
promise是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理强大
它由社区最早提出和实现,ES6将其写入了语言标准,统一了用法,原生提供了promise对象
所谓promise,简单说就是一个容器,里面保存着某个未来才会结束的时间(通常是一个异步操作)的结果
从语法上说,promise是一个对象,它可以获取异步操作的消息
promise提供统一的API,各种异步操作都可以用同样的方法进行处理
有了promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数
此外,promise对象提供了统一的接口,使得控制异步操作更加容易
2、基本用法
promise构造函数接收一个函数作为参数,该函数的两个参数分别是resolve和reject
它们是两个函数,有JavaScript引擎提供,不用自己部署
resolve函数的作用是:将promise对象的状态从“未完成”变为“成功”(即从pending变成resolved)
在异步操作成功时调用,并将异步操作的结果作为参数传递出去
reject函数的作用是:将promise对象的状态从“未完成”变为“失败”(即从pending变成rejected)
在异步操作失败时调用,并将异步操作的结果作为参数传递出去
如果调用resolve和reject函数时带有参数,那么它们的参数会被传递给回调函数
promise实例生成以后,可以调用then方法分别指定resolved状态和rejected状态的回调函数
.then(function(){
//success
},
function(){
//error
}
)
3、promise.prototype.then()
then方法是定义在原型对象promise.prototype上的
它的作用是为promise实例添加状态改变时的回调函数
then方法的第一个参数时resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数
then方法返回的是一个新的promise实例(注意:不是原来的promise实例)
因此可以采用链式方法,即在then方法后面再调用另一个then方法
第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数
4、promise.prototype.catch()
Promise.prototype.catch方法是.then(null,rejection)的别名,用于指定发生错误时的回调函数
一般来说,不要在then方法里面定义reject状态的回调函数(即then的第二个参数),总是使用catch方法
promise对象的错误具有“冒泡”性质,会一直向后传递,知道被捕获为止
也就是说,错误总会被下一个catch语句捕获
5、promise.all()
Promise.all方法用于将读个Promise实例包装秤一个新的promise实例
var p = Promise.all([p1,p2,p3])
上面代码中,Promise.all方法接收一个数组作为参数,p1,p2,p3都是promise实例
p的状态由p1,p2,p3决定,分为2种情况:
1)只有p1,p2,p3的状态都变为fulfilled,p的状态才会变成fulfilled,
此时p1,p2,p3的返回值组成一个数组,传递给p的回调函数
2)只要p1,p2,p3之中有一个被rejected,p的状态就会变成rejected
此时第一个被rejected的实例的返回值,会传递给p的回调函数
6、promise.race()
promise.race方法同样是将多个promise实例包装成一个新的promise
下面代码中,只要p1,p2,p3之中有一个实例率先改变状态,p的状态就跟着改变
那个率先改变的promise实例的返回值,就传递给p的回调函数
var p = Promise.race([p1,p2,p3])
7、promise.resolve()
promise.resolve方法将现有对象转为Promise对象,例如:
var jsPromise = promise.resolve($.ajax('whatever.json'))
1)参数时一个promise实例
promise.resolve将不做任何修改、原封不动的返回这个实例
2)参数时一个thenable对象
thenable对象是指具有then方法的对象,promise.resolve方法会将这个对象转为promise对象
然后立即执行thenable对象的then方法
3)参数不是具有then方法的对象,或根本就不是对象
如果参数是一个原始值,或者是一个不具有then方法的对象
则promise.resolve方法返回一个新的promise对象,状态为resolved
4)不带有任何参数
直接返回一个resolve状态的promise对象
需要注意的是:立即resolve的promise对象,是在本轮“事件循环”(event loop)结束时
而不是在下一轮的“事件循环”的开始时
8、promise.reject()
promise.reject方法也会返回一个新的promise实例,该实例状态为rejected
var p = promise.reject("出错了")
=>
var p = new Promise((resolve,reject)=>reject('出错了'))
9、finally()
finally方法用于指定不敢promise对象最后无论如何都会执行的操作
它接收一个普通的回调函数作为参数,该函数不管怎么样都必须执行
下面是一个例子,服务器使用promise处理请求,然后使用finally方法关掉服务器
server.listen(0).then(function(){
//run test
}).finally(server.stop)
第五章 ES6模块
一、介绍
历史上,JavaScript一直没有模块(modules)体系,无法将一个大程序拆分成相互依赖的小文件,再用简单的方法拼接装起来
在ES6之前,社区定制了一些模块加载方案,最主要的有commonJS和AMD两种。前者用于服务器,后者用于浏览器
ES6在语言标准的层面上,实现了模块功能,而且实现的相当简单,完全可以取代commonJS和AMD规范,成为浏览器和服务器通用的模块解决方案
二、export命令
模块功能主要由两个命令构成:export和import
export命令用于规定模块的对外接口
import命令用于输入其他模块提供的接口
一个模块就是一个独立的文件,该文件内部的所有变量,外部无法获取
如果你希望外部能够读取模块内部的某个变量,就必须适应使用export关键字输出该变量
下面是一个js文件,里面使用export命令输出变量
var firstName = 'Jennie';
var lastName = 'Kim';
var year = 1996'
function multiply(x,y){return x*y;};
export{firstName,lastName,year,multiply};
需要特别注意的是:export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系,不能直接导出一个值
export var m = 1;
或 var m = 1;export{m};
或 var n = 1;export{n as m};
在一个模块中,export可以调用多次
三、import命令
使用export命令定义了命令的对外接口以后,其他js文件就可以通过import命令加载这个模块
1)解构导入
import {firstName,lastName,year} from './profile';
2)重命名变量
import {lastName as surname} from './profile';
3)重复导入
import {name} from './module1';
inport {age} from './module1';
如果多次重复执行同一import语句,那么只会执行一次模块代码
4)模块的整体加载
import * as person from './module1'
四、export default命令
使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载
但是,用户肯定希望快速上手,未必愿意阅读文档去了解模块有哪些属性和方法
为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令为模块指定默认输出
export default function(){
console.log('foo')
}
其他模块加载该模块时,import命令可以为该匿名函数指定任意名字
import customName from './export-default';
customName(); //'foo'
export default命令用于指定模块的默认输出
显然,一个模块只能有一个默认输出,因此export default命令只能用一次
所以,import命令后面才不用加大括号,因为只可能对应一个方法或对象
五、export与import复合写法
如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起
export {foo,bar} from './my_module';
=>
import {foo,bar} from 'my_module';
export {foo,bar}
第六章 Class
一、简介
JavaScript语言中,生成实例对象的传统方法是通过构造函数,ES6提供了更接近传统语言的写法,引入了class(类)概念,作为对象的模板。通过class关键字,就可以定义类。
基本上,ES6的class可以看做只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰更像面向对象编程的语法而已。所以ES6的类,完全可以看作构造函数的另一种写法。
class Point{
constructor(x,y){
this.x = x;
this.y = y;
}
toString(){
return '('+this.x+','+this.y+')';
}
}
二、方法
在类中可以直接定义方法,实际上类的所有方法都定义在类的prototype属性上面
在类的实例上面调用方法,其实就是调用原型上的方法
class Ponit{
constructor(){//...}
toString(){//...}
toValue(){//...}
}
由于类的方法都定义在prototype对象上面,所以类的新方法都可以添加在prototype对象上面。
Object.assign方法可以很方便地向类添加多个方法
class Point{
constructor(){//...}
}
Object.assign(Point.prototype,{
toString(){},
toVlaue(){}
})
三、constructor方法
constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法
一个类必须有constructor方法,如果没有显示定义,一个空的constructor方法会被默认添加
class Point{}
=>
class Point {constructor(){}}
类必须使用new调用,否则会报错
这是它跟普通构造函数的一个主要区别,后者不用new也可以执行
四、静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承
如果在一个方法钱,加上static关键字,就表示该方法不会被实例继承
而是直接通过类来直接调用,这就称为“静态方法”
class Foo{
static classMethod(){return 'hello';}
}
Foo.classMethod(); //'hello'
如果静态方法包含this关键字,这个this值得是类,而不是实例
五、实例属性
类的实例属性可以定义在构造函数中
class Person{
constructor(id,name,age){
this.id = id;
this.name = name;
this.age = age;
}
}
六、静态属性
直接在类上定义的属性是静态属性
class Foo{
//...
}
Foo.prop = 1;
Foo.prop; //1
目前,只有这种写法可行,因为ES6明确规定,class内部只有静态方法,没有静态属性
七、继承
class可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承要清晰和方便很多
class Animal{
constructor(name){
this.name = name;
}
sayName(){
console.log("my name is",this.name)
}
}
class Dog extends Animal{
//...
}
子类必须在constructor方法中调用super方法,否则新建实例时会报错
这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工
如果不调用super方法,子类就得不到this对象。子类构造函数可以省略
在子类构造函数中,只有调用super之后,才可以使用this关键字,否则会报错
八、super
super这个关键字,既可以当做函数使用,也可以当做对象使用
在这两种情况下,它的用法完全不同
1)函数
子类B的构造函数中的super(),代表调用父类的构造函数
super虽然代表了父类A的构造函数,但是返回的是子类B的实例
即super内部的this指的是B,因此super()在这里相当于A.prototype.constructor.call(this)
2)对象
在普通方法中,指向佛雷德原型对象
在静态方法中,指向父类
由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的
ES6规定,通过super调用父类方法时,super会绑定子类的this
super.print()
=>
super.print.call(this)
不能直接打印super,因为无法得知super到底是函数还是对象
九、类的prototype属性和__proto__属性
class作为构造函数的语法糖,同时又prototype属性和__proto__属性,因此同时存在两条继承链
1)子类的__proto__属性,表示构造函数的继承,总是指向父类
2)子类的prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性
class A{}
class B extends A{}
B.prototype.__proto__ === A.prototype; //true
B.prototype.__proto__ === A.prototypr; //true
类的继承是按照下面的模式实现的
class A{}
class B{}
//B的实例继承A的实例
Object.setPrototypeOf(B.prototype,A.prototype);
//B的实例继承A的静态属性
Object.setPrototypeOf(B,A)
const b = new B();