ES6(4)
1 、Set 和 WeakSet用法
1.1 什么是set
Set是ES6给开发者带来的一种新的数据结构,你可以理解为值的集合。我们平时见到的数组Array也是一种数据结构,但是Set跟其他数据结构不同的地方就在于:它的值不会有重复项。
1.2 set的基本用法
Set的用法:
var s = new Set();
console.log(s);
//打印结果:Set {}
Set本身是一个构造函数,你可以理解为一个类,使用的时候需要用new来创建一个实例。以上的案例就是这样创建出一个Set结构,我们打印出来看看,控制台输出:Set{ }。
它不是值的集合吗,那如果我们想给Set对象增加一些值成员,我们要怎么做?
你可以这样:
var s = new Set([1,2,3]);
console.log(s);
//打印结果:Set {1, 2, 3}
案例中,用数组[ 1,2,3 ]作为参数传入,得到的变量s为Set { 1, 2, 3 }。
除了上面的方法,你还可以这样:
var s = new Set();
//使用add方法添加成员
s.add(1);
s.add(2);
s.add(3);
console.log(s);
//打印结果:Set {1, 2, 3}
如果你不想用数组作为参数来传值初始化变量s,你还可以通过Set 结构提供的add方法来添加方法,也是没问题的。
1.3 成员值唯一
不过,为Set 结构添加成员值的时候,要注意一点是,set结构的成员值是没有重复的,每个值都是唯一的,如果人为地给它添加相同的成员值,会发生什么呢?
var s = new Set();
s.add(1);
s.add(1);
console.log(s);
//打印结果: Set {1}
很明显,set结构会自动忽略相同的值,只会保留一个相同的值。
Set 结构除了提供add方法用于添加成员以外,还提供了哪些方法和属性呢?
1.4 size属性
size属性:获取成员的个数。
let s = new Set([1,2,3,4]);
s.size; //结果:4
结果为4,跟我们预期一致,很简单。
1.5 delete属性
delete( )方法:用户删除Set结构中的指定值,删除成功返回:true,删除失败返回:fasle。
//用数组作为参数初始化
var s = new Set([1,2,3]);
console.log(s);
//打印结果:Set {1, 2, 3}
//使用delete方法删除指定值
s.delete(2);//结果:true
s.delete(4);//结果:false
s.delete(2);//结果:true
s.delete(4);//结果:false
console.log(s);
//打印结果:Set {1, 3}
案例中,我们使用delete( )方法删除了指定值:2,结果返回true。删除指定值:4的时候,返回false,原因是变量s中找不到数字4。
当Set结构的成员比较多,你觉得一个一个删除比较麻烦的时候,你可以用下面这个方法。
//打印结果:Set {1, 3}
案例中,我们使用delete( )方法删除了指定值:2,结果返回true。删除指定值:4的时候,返回false,原因是变量s中找不到数字4。
当Set结构的成员比较多,你觉得一个一个删除比较麻烦的时候,你可以用下面这个方法。
1.6 clear方法
clear( )方法:清除所有成员。
//用数组作为参数初始化
var s = new Set([1,2,3]);
console.log(s);
//打印结果:Set {1, 2, 3}
s.clear();
console.log(s);
//打印结果:Set {}
上面的代码中,在使用clear( )方法之前和之后都打印了变量s的值,我们看到,clear( )方法使用之后,Set结构的成员都被清除了,一个不留。
console.log(s);
//打印结果:Set {}
上面的代码中,在使用clear( )方法之前和之后都打印了变量s的值,我们看到,clear( )方法使用之后,Set结构的成员都被清除了,一个不留。
1.7 has方法
has( )方法:判断set结构中是否含有指定的值。如果有,返回true;如果没有,返回fasle。
//用数组作为参数初始化
var s = new Set([1,2,3]);
s.has(1);//结果:true
s.has(4);//结果:false
当判断变量s中是否含有数字1时,得到的结果是true,判断是否含有数字4的时候,得到的结果是false。
1.8 enteries方法
entries( )方法:返回一个键值对的遍历器。
//用数组作为参数初始化
var s = new Set(['a','b','c']);
s.entries();
//结果:SetIterator {["a", "a"], ["b", "b"], ["c", "c"]}
注意得到的结果,成员值“a”对应的键值对是[“a”,”a”],也就是说:Set结构是键名和键值是同一个值。
1.9 keys和values方法
keys( )方法:返回键名的遍历器。
values( )方法:返回键值的遍历器。
//用数组作为参数初始化
var s = new Set(['a','b','c']);
s.keys();
//结果:SetIterator {"a", "b", "c"}
//结果:SetIterator {"a", "b", "c"}
s.values();
//结果:SetIterator {"a", "b", "c"}
我把两个函数放在一起演示,是因为上面entries()方法的使用告诉我们,Set结构的键名和键值是同一个值,那么我们就用Set结构提供的keys( )和values()方法来检验一下。从得到的结果可以看出:两者确实一致。
//用数组作为参数初始化
var s = new Set(['a','b','c']);
//用for...of遍历
for(let [i,v] of s.entries()){
console.log(i+' '+v);
}
//打印结果:a a
// b b
// c c
//结果:SetIterator {"a", "b", "c"}
我把两个函数放在一起演示,是因为上面entries()方法的使用告诉我们,Set结构的键名和键值是同一个值,那么我们就用Set结构提供的keys( )和values()方法来检验一下。从得到的结果可以看出:两者确实一致。
//用数组作为参数初始化
var s = new Set(['a','b','c']);
//用for...of遍历
for(let [i,v] of s.entries()){
console.log(i+' '+v);
}
//打印结果:a a
// b b
// c c
1.10 foreach方法
forEach( )方法:遍历每一个成员。
//用数组作为参数初始化
var s = new Set(['a','b','c']);
//使用回调函数遍历每个成员
s.forEach(function(value,key){
console.log(value,key)
});
//打印结果:a a
// b b
// c c
使用方式跟数组的forEach一样。当然,得到的value是key的值是一样的。
1.11 set用途之一
利用Set结构的成员值不能重复的特点,我们可以轻松的完成一些效果。
比如:有一天面试官要小菜实现数组去重的效果。
这个时候,小菜想起了ES6的Set结构的特性,再也不需要去遍历数组的元素,然后再一个个对比了。 然后自信地写起下了代码:
//目标数组arr,要求去重
let arr = [1,2,2,3,4,4,4];
let s = new Set(arr);
//结果:Set {1,2,3,4}
let newArr = Array.from(s);
//结果:[1,2,3,4],完成去重
上面的代码重点是一个含有重复元素的数组,作为参数,用于初始化一个Set实例对象,因为Set结构不会含有重复成员,就会自动忽略相同的元素,只保留一个相同的值。
1.12 Weakset结构
讲到Set结构,就不能不讲WeakSet结构。Set和WeakSet两者名字就很像,注定它们有很多一样的地方。
WeakSet结构同样不会存储重复的值,不同的是,它的成员必须是对象类型的值。
//初始化一个WeakSet对象
let ws = new WeakSet([{"age":18}]);
//结果:WeakSet {Object {age: 18}}
我们初始化一个WeakSet对象,参数一个数组,数组的成员必须是对象类型的值{"age":18},否则就会报错。
//初始化一个WeakSet对象
let ws = new WeakSet([1,2]);
//结果:报错
以上的写法就会报错,因为数组的元素不是对象类型的,是数字1,2。
实际上,任何可遍历的对象,都可以作为WeakSet的初始化参数。比如:数组。
let arr1 = [1];
let arr2 = [2];
//初始化一个WeakSet对象,参数是数组类型
let ws = new WeakSet([arr1,arr2]);
//结果:WeakSet {Object {age: 18}}
同样,WeakSet结构也提供了add( ) 方法,delete( ) 方法,has( )方法给开发者使用,作用与用法跟Set结构完全一致。
另一个不同点是:WeakSet 结构不可遍历。因为它的成员都是对象的弱引用,随时被回收机制回收,成员消失。所以WeakSet 结构不会有keys( ),values( ),entries( ),forEach( )等方法和size属性。
2 、Map和WeakMap用法
什么是Map?
介绍什么是Map,就不得不说起Object对象,我们都知道Object对象是键值对的集合:
//Object对象
{"name":"前端君","gender":1}
ES5中的key键名的类型要求一定是字符串,当然,ES6已经允许属性名的类型是Symbol。在,ES6 提供了Map结构给我们使用,它跟Object对象很像,但是不同的是,它的key键名的类型不再局限于字符串类型了,它可以是各种类型的值;可以说,它比Object对象更加灵活了,当然,也更加复杂了。
Map的基本用法
Map结构提供了一个构造函数给我们,我们使用的时候需要用new来创建实例:
let m = new Map();
如果想要在创建实例的同时,初始化它的内容,我们可以给它传参,形式跟Set结构类型,都是需要用数组作为参数,我们来试试看看:
let m = new Map([
["name","前端君"],
["gender",1]
]);
console.log(m);
//打印结果:Map {"name" => "前端君", "gender" => 1}
大家注意Map( )方法里面的参数,首先它是一个数组,而里面的内容也是由多个数组组成,“name”:“前端君”作为一个键值对,将它们装在一个数组里面,[“name”:“前端君”],另外一组键值对也一样:[“gender”:1 ]。这就是初始化一个Map结构实例的基本写法。
初始化成实例后,Map结构还提供了一些实例的属性和方法供我们实现对实例的操作。我们一起看看都有哪些属性和方法。
set方法
set( )方法作用:给实例设置一对键值对,返回map实例。
let m = new Map();
//set方法添加
//添加一个string类型的键名
m.set("name","前端君");
//添加一个数字类型的键名
m.set(1,2);
console.log(m);
//打印结果:Map {"name" => "前端君", 1 => 2}
set方法的使用很简单,只需要给方法传入key和value作为键名和键值即可。注意:第三行代码中,我们传入的key是数字1,这就证明了,Map结构确实可以存储非字符串类型的键名,当然你还可以设置更多其它类型的键名,比如:
//数组类型的键名
m.set([1],2);
//对象类型的键名
m.set({"name":"Zhangsan"},2);
//布尔类型的键名
m.set(true,2);
//Symbol类型的键名
m.set(Symbol('name'),2);
//null为键名
m.set(null,2);
//undefined为键名
m.set(undefined,2);
以上6种类型值都可以作为键名,可以成功添加键值对,没毛病。
使用set方法的时候有一点需要注意,如果你设置一个已经存在的键名,那么后面的键值会覆盖前面的键值。我们演示一下:
let m = new Map();
m.set("name","前端君");
console.log(m);
//结果:Map {"name" => "前端君"}
//再次设置name的值
m.set("name","隔壁老王");
console.log(m);
//结果:Map {"name" => "隔壁老王"}
上面的案例,我们第一次把name的值设置为“前端君”,当再次使用set方法设置name的值的时候,后者成功覆盖了前者的值,从此“前端君” 变 “隔壁老王”。
get方法
get( )方法作用:获取指定键名的键值,返回键值。
let m = new Map([["name","前端君"]]);
m.get("name");//结果:前端君
m.get("gender");//结果:undefined
get方法使用也很简单,只需要指定键名即可。获取存在对应的键值,如果键值对存在,就会返回键值;否则,返回undefined,这个也很好理解。
delete方法
delete( )方法作用:删除指定的键值对,删除成功返回:true,否则返回:false。
let m = new Map();
m.set("name","前端君");
//结果:Map {"name" => "前端君"}
m.delete("name");//结果:true
m.delete("gender");//结果:false
我们使用delete方法,删除“name”的时候成功,返回了true。删除“gender”的时候,由于Map结构中不存在键名:“gender”,所以删除失败,返回false。
clear方法
跟Set结构一样,Map结构也提供了clear( )方法,让你一次性删除所有键值对。
let m = new Map();
m.set("name","前端君");
m.set("gender",1);
m.clear();
console.log(m);
//打印结果:Map {}
使用clear方法后,我们再打印一下变量m,发现什么都没有,一个空的Map结构,说明clear方法起作用了。
has方法
has( )方法作用:判断Map实例内是否含有指定的键值对,有就返回:true,否则返回:false。
let m = new Map();
m.set("name","前端君");
m.has('name');//结果:true
m.has('age');//结果:false
Map实例中含有键名:name,就返回了true,键名age不存在,就返回false。好理解吧,比较简单。
entries方法
entries( )方法作用:返回实例的键值对遍历器。
let m = new Map([
["name","前端君"],
["age",25]
]);
for(let [key,value] of m.entries()){
console.log(key+' '+value);
}
//打印结果:name 前端君
// age 25
案例中的 m.entries( ) 返回键值对的遍历器,使用了for...of来遍历这个遍历器,得到的值分别赋值到key和value,然后控制台分别输出它们。
keys和values方法
keys( )方法:返回实例所有键名的遍历器。
values( ) 方法:返回实例所有键值的遍历器。
既然都是遍历器,那就用for...of把它们遍历出来吧:
let m = new Map([
["name","前端君"],
["age",25]
]);
//使用keys方法获取键名
for(let key of m.keys()){
console.log(key);
}
//打印结果:name
//age
//使用values方法获取键值
for(let value of m.values()){
console.log(value);
}
//打印结果:前端君
// 25
keys方法和values方法的使用方式一致,只是返回的结果不同。
forEach方法
除了使用以上三个方法实现遍历以外,我们还可以使用forEach遍历每一个键值对:
let m = new Map([
["name","前端君"],
["age",25]
]);
m.forEach(function(value,key){
console.log(key+':'+value);
});
//打印结果:name:前端君
// age:25
forEach方法接收一个匿名函数,给匿名函数传参value和key,分别是Map实例的键名和键值,这个方法的使用相信大家一定不会陌生。
size属性
其中一个常用的属性就是size:获取实例的成员数。
let m = new Map();
m.set(1,3);
m.set('1','3');
m.size;//结果:2
使用set方法给实例m添加了两个键值对成员,所以实例的 size为:2。
什么是WeakMap?
WeakMap结构和Map结构很类似,不同点在于WeakMap结构的键名只支持引用类型的数据。哪些是引用类型的值呢?比如:数组,对象,函数。
关于什么是引用类型,其中涉及到了传址和传值的区别,还记得装修工张师傅和王师傅的例子吗?
WeakMap的基本用法
WeakMap结构的使用方式和Map结构一样:
let wm = new WeakMap();
两者都是使用new来创建实例。如果添加键值对的话,我们同样是使用set方法,不过键名的类型必须要求是引用类型的值,我们来看看:
let wm = new WeakMap();
//数组类型的键名
wm.set([1],2);
//对象类型的键名
wm.set({'name':'Zhangsan'},2);
//函数类型的键名
function fn(){};
wm.set(fn,2);
console.log(wm);
//打印:WeakMap {
[1] => 2,
Object {name: "Zhangsan"} => 2,
function => 2
}
从打印结果可以看出,以上类型的键名都可以成功添加到WeakMap实例中。
WeakMap与Map的区别?
如果是普通的值类型则不允许。比如:字符串,数字,null,undefined,布尔类型。而Map结构是允许的,这就是两者的不同之处,谨记。
跟Map一样,WeakMap也拥有get、has、delete方法,用法和用途都一样。不同地方在于,WeakMap不支持clear方法,不支持遍历,也就没有了keys、values、entries、forEach这4个方法,也没有属性size。
理由跟WeakSet结构一样:键名中的引用类型是弱引用,你永远不知道这个引用对象什么时候会被垃圾回收机制回收了,如果这个引用类型的值被垃圾机制回收了,WeakMap实例中的对应键值对也会消失。
3 、ES6的Promise对象
Promise设计初衷
你需要用ajax进行多次请求,而且,每次请求都依赖上一次请求返回的数据来作为参数,然后继续发出请求,你把代码写成了这样:
//------请求A 开始---------
$.ajax({
success:function(res1){
//------请求B 开始----
$.ajax({
success:function(res2){
//----请求C 开始---
$.ajax({
success:function(res3){
}
});
//---请求C 结束---
}
});
//------请求B 结束-----
}
});
//------请求A 结束---------
上面的案例,假设请求C需要依赖请求B返回的数据,所以,C只能放在B的success函数内;B需要依赖A请求得到的数据作为参数,所以,B只能放在A的success函数内;也就是:请求A包含着请求B,请求B又包含了请求C。
就这样,请求顺序为:请求A -> 请求B -> 请求C,最后你也能顺利的完成了任务。
传统写法的缺点:
1. 如果存在多个请求操作层层依赖的话,那么以上的嵌套就有可能不止三层那么少了,加上每一层还会有复杂的业务逻辑处理,代码可读性也越来越差,不直观,调试起来也不方便。如果多人开发的时候没有足够的沟通协商,大家的代码风格不一致的话,更是雪上加霜,给后面的维护带来极大的不便。
2. 如果请求C的需要依赖A和B的结果,但是请求A和B缺互相独立,没有依赖关系,以上的实现方式,就使得B必须得到A请求完成后才可以执行,无疑是消耗了更多的等待时间。
既然使用这种回调函数层层嵌套(又称:回调地狱)的形式存在缺点,ES6想了办法治它,所以就有了Promise的出现了。
那么我们就知道了:Promise对象能使我们更合理、更规范地进行处理异步操作。
Promise基本用法
我们就看看它的基本用法:promise 承诺
Promise对象是全局对象,你也可以理解为一个类,创建Promise实例的时候,要有那个new关键字。参数是一个匿名函数,其中有两个参数:resolve(解决)和reject(拒绝),两个函数均为方法。resolve方法用于处理异步操作成功后业务;reject方法用于操作异步操作失败后的业务。
Promise的三种状态
Promise对象有三种状态:
1.pending:刚刚创建一个Promise实例的时候,表示初始状态;
2.fulfilled:resolve方法调用的时候,表示操作成功;
3.rejected:reject方法调用的时候,表示操作失败;
状态只能从 初始化 -> 成功 或者 初始化 -> 失败,不能逆向转换,也不能在成功fulfilled
和失败rejected之间转换。
let pro = new Promise(function(resolve,reject){
//实例化后状态:pending
if('操作成功'){
resolve();
//resolve方法调用,状态为:fulfilled
}else{
reject();
//reject方法调用,状态为:rejected
}
});
上面的注释,讲清楚了一个Promise实例的状态改变情况。
初始化实例后,对象的状态就变成了pending;当resolve方法被调用的时候,状态就变成了:成功fulfilled;当reject方法被调用的时候,状态就会有pending变成失败rejected。
Then方法
了解了Promise的创建和状态,我们来学习一个最重要的实例方法:then( )方法。
then( )方法:用于绑定处理操作后的处理程序。
pro.then(function (res) {
//操作成功的处理程序
},function (error) {
//操作失败的处理程序
});
参数是两个函数,第一个用于处理操作成功后的业务,第二个用于处理操作异常后的业务。
Catch方法
对于操作异常的程序,Promise专门提供了一个实例方法来处理:catch( )方法。
pro.catch(function (error) {
//操作失败的处理程序
});
catch只接受一个参数,用于处理操作异常后的业务。
综合上面的两个方法,大家都建议将then方法用于处理操作成功,catch方法用于处理操作异常,也就是:
pro.then(function (res) {
//操作成功的处理程序
}).catch(function (error) {
//操作失败的处理程序
});
之所以能够使用链式调用,是因为then方法和catch方法调用后,都会返回promise对象。
讲了那么多,如果你之前一点都没接触过Promise的话,现在一定很懵逼,没关系,下面我们用一个案例来串联前面的知识点,演示一下,认真阅读注释:
//用new关键字创建一个Promise实例
let pro = new Promise(function(resolve,reject){
//假设condition的值为true
let condition = true;
if(condition){
//调用操作成功方法
resolve('操作成功');
//状态:pending->fulfilled
}else{
//调用操作异常方法
reject('操作异常');
//状态:pending->rejected
}
});
//调用操作成功方法
resolve('操作成功');
//状态:pending->fulfilled
}else{
//调用操作异常方法
reject('操作异常');
//状态:pending->rejected
}
});
//用then处理操作成功,catch处理操作异常
pro.then(function (res) {
pro.then(function (res) {
//操作成功的处理程序
console.log(res)
console.log(res)
}).catch(function (error) {
//操作失败的处理程序
console.log(error)
console.log(error)
});
//控制台输出:操作成功
上面案例的注释十分详细,串联起了上面介绍的所有知识点:创建实例,状态转换,then方法和catch方法的使用。
由于我们设置了变量condition的值为true,所以执行后控制台输出的结果是:“操作成功”。
案例
我们看看下面的案例:
let pro = new Promise(function(resolve,reject){
if(true){
//调用操作成功方法
resolve('操作成功');
}else{
//调用操作异常方法
reject('操作异常');
}
});
//控制台输出:操作成功
上面案例的注释十分详细,串联起了上面介绍的所有知识点:创建实例,状态转换,then方法和catch方法的使用。
由于我们设置了变量condition的值为true,所以执行后控制台输出的结果是:“操作成功”。
案例
我们看看下面的案例:
let pro = new Promise(function(resolve,reject){
if(true){
//调用操作成功方法
resolve('操作成功');
}else{
//调用操作异常方法
reject('操作异常');
}
});
//用then处理操作成功,catch处理操作异常
pro.then(requestA)
.then(requestB)
.then(requestC)
.catch(requestError);
pro.then(requestA)
.then(requestB)
.then(requestC)
.catch(requestError);
function requestA(){
console.log('请求A成功');
return '请求B,下一个就是你了';
}
function requestB(res){
console.log('上一步的结果:'+res);
console.log('请求B成功');
return '请求C,下一个就是你了';
}
function requestC(res){
console.log('上一步的结果:'+res);
console.log('请求C成功');
}
function requestError(){
console.log('请求失败');
}
console.log('请求A成功');
return '请求B,下一个就是你了';
}
function requestB(res){
console.log('上一步的结果:'+res);
console.log('请求B成功');
return '请求C,下一个就是你了';
}
function requestC(res){
console.log('上一步的结果:'+res);
console.log('请求C成功');
}
function requestError(){
console.log('请求失败');
}
//打印结果:
//请求A成功
//上一步的结果:请求B,下一个就是你了
//请求B成功
//上一步的结果:请求C,下一个就是你了
//请求C成功
案例中,先是创建一个实例,还声明了4个函数,其中三个是分别代表着请求A,请求B,请求C;有了then方法,三个请求操作再也不用层层嵌套了。我们使用then方法,按照调用顺序,很直观地完成了三个操作的绑定,并且,如果请求B依赖于请求A的结果,那么,可以在请求A的程序用使用return语句把需要的数据作为参数,传递给下一个请求,案例中我们就是使用return实现传递参数给下一步操作的。
//请求A成功
//上一步的结果:请求B,下一个就是你了
//请求B成功
//上一步的结果:请求C,下一个就是你了
//请求C成功
案例中,先是创建一个实例,还声明了4个函数,其中三个是分别代表着请求A,请求B,请求C;有了then方法,三个请求操作再也不用层层嵌套了。我们使用then方法,按照调用顺序,很直观地完成了三个操作的绑定,并且,如果请求B依赖于请求A的结果,那么,可以在请求A的程序用使用return语句把需要的数据作为参数,传递给下一个请求,案例中我们就是使用return实现传递参数给下一步操作的。
Promise.all方法
Promise.all( )方法:接受一个数组作为参数,数组的元素是Promise实例对象,当参数中的实例对象的状态都为fulfilled时,Promise.all( )才会有返回。
//创建实例pro1
let pro1 = new Promise(function(resolve){
setTimeout(function () {
resolve('实例1操作成功');
},5000);
});
//创建实例pro2
let pro2 = new Promise(function(resolve){
setTimeout(function () {
resolve('实例2操作成功');
},1000);
});
Promise.all([pro1,pro2]).then(function(result){
console.log(result);
});
//打印结果:["实例1操作成功", "实例2操作成功"]
上述案例,我们创建了两个Promise实例:pro1和pro2,我们注意两个setTimeout的第二个参数,分别是5000毫秒和1000毫秒,当我们调用Promise.all( )方法的时候,会延迟到5秒才控制台会输出结果。
因为1000毫秒以后,实例pro2进入了成功fulfilled状态;此时,Promise.all( )还不会有所行动,因为实例pro1还没有进入成功fulfilled状态;等到了5000毫秒以后,实例pro1也进入了成功fulfilled状态,Promise.all( )才会进入then方法,然后在控制台输出:["实例1操作成功","实例2操作成功"]。
这个方法有什么用呢?一般这样的场景:我们执行某个操作,这个操作需要得到需要多个接口请求回来的数据来支持,但是这些接口请求之前互不依赖,不需要层层嵌套。这种情况下就适合使用Promise.all( )方法,因为它会得到所有接口都请求成功了,才会进行操作。
Promise.race方法
Promise.race()方法:它的参数要求跟Promise.all( )方法一样,不同的是,它参数中的promise实例,只要有一个状态发生变化(不管是成功fulfilled还是异常rejected),它就会有返回,其他实例中再发生变化,它也不管了。
//初始化实例pro1
let pro1 = new Promise(function(resolve){
setTimeout(function () {
resolve('实例1操作成功');
},4000);
});
//初始化实例pro2
let pro2 = new Promise(function(resolve,reject){
setTimeout(function () {
reject('实例2操作失败');
},2000);
});
let pro2 = new Promise(function(resolve,reject){
setTimeout(function () {
reject('实例2操作失败');
},2000);
});
Promise.race([pro2,pro1]).then(function(result){
console.log(result);
}).catch(function(error){
console.log(error);
});
//打印结果:实例2操作失败
同样是两个实例,实例pro1不变,不同的是实例pro2,这次我们调用的是失败函数reject。
由于pro2实例中2000毫秒之后就执行reject方法,早于实例pro1的4000毫秒,所以最后输出的是:实例2操作失败。
以上就是对Promise对象的内容讲解,上面提到了一个概念:回调地狱;指的是过多地使用回调函数嵌套,使得调试和维护起来极其的不便。
console.log(result);
}).catch(function(error){
console.log(error);
});
//打印结果:实例2操作失败
同样是两个实例,实例pro1不变,不同的是实例pro2,这次我们调用的是失败函数reject。
由于pro2实例中2000毫秒之后就执行reject方法,早于实例pro1的4000毫秒,所以最后输出的是:实例2操作失败。
以上就是对Promise对象的内容讲解,上面提到了一个概念:回调地狱;指的是过多地使用回调函数嵌套,使得调试和维护起来极其的不便。
4 、类基本用法
4.1 类的属性和方法
声明一个类的写法:
代码很简短,我们通过关键字class来声明一个名字叫Animal的类,可以看到类里面(花括号 {}里面)有一个叫constructor(构造、建造)方法,它就是构造方法,构造方法里面的this,指向的是该类实例化后的对象,这就是实现了一个类的声明。
其中,构造方法constructor是一个类必须要有的方法,默认返回实例对象;创建类的实例对象的时候,会调用此方法来初始化实例对象。如果你没有编写constructor方法,执行的时候也会被加上一个默认的空的constructor方法。
4.2 类的实例化对象
了解了类的声明和constructor构造函数的特点,我们下面来了解如何给类添加属性和方法。
我们把类名后面的括号{ }里面的内容称之为类体,我们会在类体内来编写类的属性和方法。上面的案例中,类体内有2个方法:constructor( )、getName()。
其中constructor方法是构造方法,在实例化一个类的时候被调用。constructor方法是必须的,也是唯一的,一个类体不能含有多个constructor构造方法。我们可以在方法里面自定义一些对象的属性,比如案例中的name属性。
此外,我们还自定义了一个getName( )方法,它属于类的实例方法,实例化后对象可以调用此方法。
4.3 类的静态方法
掌握了类的属性和方法的写法,接下来,我们学习如何创建对象和使用对象的实例方法:
还是同一个类Animal,我们通过new来创建了实例对象dog,构造方法会把传进去的参数“dog”通过this.name赋值给对象的name属性,所以dog的name属性为“dog”,对象dog还可以调用自己的实例方法getName( ),结果返回:“This is a dog”。
实例对象的创建有几个要注意的事项:
1. 必须使用new创建字来创建类的实例对象
2.先声明定义类,再创建实例,否则会报错
4.4 类的继承
说到类class,就不得不说类的继承,ES6使用extends关键字来实现子类继承父类,我们来演示一下:
上面的案例中,我们定义两个类,Animal类作为父类,Dog类作为子类,然后通过关键字extends来实现继承,此外,我们还注意到一个关键字super,它相当于是父类中的this。
我们可以用super来引用父类,访问父类的方法,我们来演示一下:
在父类中,我们定义了say方法,想要在子类中调用父类的say方法的话,我们使用super.say( )即可实现。
使用super有几个要注意的事项:
1. 子类必须在constructor方法中调用super方法
2. 调用super( ),才可以使用this,否则报错
以上就是关于类继承的介绍,重点在于关键字extends和super,尤其是super的理解和使用,大家需要理解透彻。
5 、Module模块
5.1 模块化的初衷
现在的web系统越来越庞大、复杂,需要团队分工,多人协作,大型系统的javascript文件经常存在复杂的依赖关系,后期的维护成本会越来越高。
JavaScript模块化正式为了解决这一需求而诞生。
竟然模块化这么重要,我们看看ES6的module模块是什么实现的?
Ps:目前还没有浏览器支持ES6的module模块。
假设现在有两个js文件,分别是module-A.js和module-B.js,我们把它们视为两个模块。带着这个假设,下面我们来学习module模块的几个概念以及它们的含义。
5.2 模块Module
模块Module:一个模块,就是一个对其他模块暴露自己的属性或者方法的文件。
在这里,我们会把module-A.js和module-B.js分别当作两个模块(moduleA模块和moduleB模块)来对待和处理。用这两个模块来演示如何暴露一个模块的属性或方法。
5.3 导出Export
导出Export:作为一个模块,它可以选择性地给其他模块暴露(提供)自己的属性和方法,供其他模块使用。
5.4 导入Import
导入Import:作为一个模块,可以根据需要,引入其他模块的提供的属性或者方法,供自己模块使用。
5.5 模块化的实现
带着这三个概念,我们来演示一下它们的基本用法:
moduleB模块代码:
模块B我们使用关键字export关键字,对外暴露了一个属性:name的值为:字符串“前端君”。一个关键字,一句代码就实现了,是不是很简单。
模块B演示了导出,接下来我们用模块A来演示如何导入。
moduleA模块代码:
模块A我们使用关键字import导入了模块B的name属性,并且赋值给变量name。关键字from的作用是指定你想要引入的模块,我们这里指定的是module-B.js文件,也就是上面的模块B。打印结果:“前端君”正是模块B的对外暴露的属性。
5.6 批量导出
对于模块B,如果你想导出(暴露)多个属性和方法的话,你可以这样实现:
上面,我们定义了2个属性和1个方法,最后用一个对象实现将它们批量导出。我们更推荐的是使用这种方法实现导出,因为当对外暴露的属性和方法较多的时候,这种方法可以更直观地看出当前模块暴露了哪些变量。
而对于这种批量导出,我们导入的时候怎么对应上呢?
同样,我们使用多个同名变量就可以获取对应的属性和方法,变量名字必须跟导出的一致才能准确获取,位置顺序无要求。
5.7 重命名导出的变量
也许你突发奇想,想给导入的变量换一个名字的话,你可以这样做:
使用关键字as,可以实现给变量name更换名字为myname。最后正确输出myname的值:“前端君”。
5.8 整体导入
我们还可以使用星号*实现整体导入:
使用星号符*将模块B提供的所有属性和方法整体导入赋值给变量obj,我们可以点运算符来获取它的属性和方法。
5.9 默认导出
默认导出,每个模块支持我们导出一个没有名字的变量,我们使用关键语句export default来实现:
我们使用export default关键字对外导出一个匿名函数,导入这个模块的时候,可以为这个匿名函数取任意的名字,我们试一下导入上面那个匿名函数:
同样是使用import关键字导入模块B,但是这次不需要使用大括号{ }。我们使用新的名字:sayDefault来代替导入的匿名函数,最后调用一下,打印结果正是模块B默认导出的匿名函数的执行效果。
5.10 注意事项
1. 声明的变量,对外都是只读的。
上面的代码片段包含了2个模块,其中,模块B导出了字符串变量name,模块A导出变量name之后试图修改它的值,结果报错。
但是,如果模块B导出的是对象类型的值,就可修改。
上面的代码片段包含了2个模块,模块B导出了对象person,模块A导入后,对其属性name进行修改,结果修改成功,这一点大家要注意,并不是所有导出的变量都不可修改,对象类型就可修改。
2. 导入不存在的变量,值为undefined。
模块A想导入的变量height,在模块B中并没有提供,但这不会抛出异常,只是height的值为undefined。