ES6 —— Promise 与 Class 类

🌈ES6 —— Promise 与 Class 类

⭕ES6 —— Promise

🔔Promise 是什么?

🐼概念

Promise是异步操作的一种解决方案

🐼什么时候使用 Promise

Promise 一般用来解决层层嵌套的回调函数(回调地狱 callback hell)的问题

🔔Promise 的基本用法

🐼1.实例化构造函数生成实例对象

1
const p = new Promise(()=>{})

🐼2.Promise 的状态

1
2
3
4
5
6
7
8
9
10
11
12
13
const p = new Promise((resolve, reject)=>{
// Promise 有3种状态,一开始是 pending(未完成),执行 resolve,变成 fulfilled(resolve),已完成
// 执行 reject,变成 rejected(reject),已失败

// Promise 的状态一旦变化,就不会再改变了

// pending --> fulfilled
// resolve();

// pending --> rejected
reject();
});
console.log(p); // 初识状态:pending , 调用 resolve:pending --> fulfilled,调用 reject:pending --> rejected

🐼3.then() 方法

1
2
3
4
5
p.then(()=>{
console.log('执行已成功的回调');
},()=>{
console.log('执行已失败的回调');
});

🐼4.resolve 和 reject 函数的参数

1
2
3
4
5
6
7
8
9
10
11
12
const p = new Promise((resolve,reject)=>{
// resolve({username:'zhangsan'});
reject(new Error('错误信息'));
})
p.then(
data=>{
console.log('success',data); // 'success' {username:'zhangsan'}
},
err=>{
console.log('error',err); // 'error' Error:'错误信息'
}
);

🔔Promise实例方法 —— then() 方法

🐼1.什么时候执行

pending –> fulfilled 时,执行 then 的第一个回调函数

pending –> rejected 时,执行 then 的第二个回调函数

🐼2.执行后的返回值

then 方法执行后返回一个新的 Promise 对象

🐼3.then 方法返回的 Promise 对象的状态改变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const p = new Promise((resolve,reject)=>{
reject(new Error('错误信息'));
});
p.then(
data=>{
console.log('succ1',data);
},
err=>{
console.log('err1',err);

// 在 then 的回调函数中,return 后面的东西,会用 Promise 包装一下
// 实际上
return undefined;
// 等价于
return new Promise(resolve=>{
resolve(undefined);
})
// 默认返回的永远都是成功状态的 Promise 对象
}
).then(
data=>{
console.log('succ2',data);
},
err=>{
console.log('err2',err);
}
)

// 结果:'err1' Error: '错误信息'
// 'succ2' undefined

🔔Promise实例方法 —— catch() 方法

🐼1.catch() 方法的作用

很少用到 then() 方法的完整写法,通常只用 then() 方法的第一个回调;于是 catch() 是专门用来处理 rejected状态的 —> 本质就是 then() 方法的一个特例:

then(null,err=>{});

🐼2.基本用法

1
2
3
4
5
6
7
8
9
new Promise((resolve, reject)=>{
reject('reason');
})
.then(data=>{
console.log(data);
})
.catch(err=>{
console.log(err); // ‘reason’
});

catch() 可以捕获它前面的错误

一般总是建议,Promise 对象后面要跟 catch() 方法,这样可以处理 Promise 内部发生的错误

🔔Promise实例方法 —— finally() 方法

🐼1.什么时候执行

当 Promise 状态发生变化时,,不论如何变化都执行,不变化不执行

🐼2.本质

finally() 本质上是 then() 的特例

1
2
3
4
5
6
7
8
9
10
11
12
13
new Promise((resolve, reject)=>{
resolve('succ');
reject('err')
})
.then(data=>{
return data;
})
.catch(err=>{
return new Promise((resolve, reject)=>{
reject(err);
})
});
// 相当于 finally() 什么都没有做

🔔Promise 构造函数的方法:Promise.resolve() 和 Promise.reject()

🐼1.Promise.resolve()

是成功状态 Promise 的一种简写形式

1
2
3
new Promise(resolve=>resolve('succ'));
// 简写
Promise.resolve('succ');

🐼2.参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 一般参数
Promise.resolve('succ').then(data=>{
console.log(data);
});

// 参数是 Promise 实例
// 当 Promise.resolve() 接收的是 Promise 对象时,直接返回这个 Promise 对象,什么都不做
const p1 = new Promise(resolve=>{
setTimeout(resolve,1000,'我执行了');
});
Promise.resolve(p1).then(data=>{
console.log(data);
});
// 等价于
p1.then(data=>{
console.log(data)
});
console.log(Promise.resolve(p1) ==== p1); // true

// 当 resolve 函数接收的是 Promise 对象时,后面的 then 会根据传递的 Promise 对象的状态变化决定执行哪一个回调

// 具有 then 方法的对象
const obj = {
then(){
console.log('then');
}
}

Promise.resolve(obj).then(
data=>{
console.log(data); // 会执行一遍 obj 对象中的 then 方法
},
err=>{
console.log(err);
}
);

🐼2.Promise.reject()

失败状态 Promise 的一种简写形式

1
2
3
4
5
6
7
8
new Promise((resolve, reject)=>{
reject('reason');
});
// 等价于
Promise.reject('reason');

// 参数
// 不管什么参数,都会原封不动的向后传递,作为后续方法的参数

🔔Promise 构造函数的方法 —— Promise.all()

🐼1.有什么用

Promise.all() 关注多个 Promise 对象的状态变化

传入多个 Promise 实例,包装成一个新的 Promise 实例返回

🐼2.基本用法

Promise.all() 的状态变化与所有传入的 Promise 实例对象状态有关

所有状态都变成 resolved,最终的状态才会变成 resolved;

只要有一个变成 rejected,最终的状态就变成 rejected

🔔Promise 构造函数的方法 —— Promise.race()

🐼1.有什么用

Promise.race() 关注多个 Promise 对象的状态变化

传入多个 Promise 实例,包装成一个新的 Promise 实例返回

🐼2.基本用法

Promise.race() 的状态取决于第一个完成的 Promise 实例对象,如果第一个完成的成功了,那最终就成功;如果第一个完成的失败了,那最终就失败

🔔Promise 构造函数的方法 —— Promise.allSettled()

🐼1.有什么用

Promise.allSettled() 关注多个 Promise 对象的状态变化

传入多个 Promise 实例,包装成一个新的 Promise 实例返回

🐼2.基本用法

Promise.allSettled() 的状态与传入的 Promise 状态无关

永远都是成功的

它只会忠实的记录下各个 Promise 的表现

🔔Promise 构造函数的方法 —— Promise.any()

🐼返回值及用法

参数中只要有一个 Promise 改变为成功状态,则返回的 Promise 状态就是成功的状态

如果参数中所有的 Promise 状态均为失败状态,则返回的 Promise 状态就是失败的状态

注意事项:

Promise.any() 不会因为某个 Promise 实例变成失败状态而结果,这个方法用于返回第一个成功的 Promoise

只要有一个 Promise 成功,此方法就会终止,它不会等待其他 Promise全部完成

💡Promise 的注意事项

🐼1.resolve 或 reject 执行后的代码

1
2
3
4
5
6
// 推荐在调用 resolve 或 reject 函数的时候加上 return,不再执行他们后面的代码
new Promise((resolve, reject)=>{
return resolve('succ'); // 最好加上 return
// reject('err');
console.log('hello'); 会被执行,但不符合书写习惯,不建议×
});

🐼2.Promise.all/race/allSettled 的参数问题

参数如果不是 Promise 数组,会将不是 Promise 的数组元素转变为 Promise 对象

不只是数组,任何可遍历的都可以作为参数

数组,字符串,Set,Map,NodeList,arguments

🐼2.Promise.all/race/allSettled 的错误处理

错误既可以单独处理,也可以统一处理

一旦被处理,就不会在其他地方再处理一遍了

💡Promise 的应用 —— 异步加载图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Promise异步加载</title>
<style>
img {
width: 100px;
margin: 100px auto;
display: block;
}
</style>
</head>
<body>
<!-- https://imgblog.yubai.eu.org/%E7%BA%A2%E7%81%AF.png -->
<!-- https://imgblog.yubai.eu.org/%E7%BB%BF%E7%81%AF.png -->
<img src="https://imgblog.yubai.eu.org/红灯.png" alt="" />
<script>
// 异步加载图片————图片预加载

// 定义加载图片的函数,返回获取成功的图片地址
const loadImgAsync = url => {
// 声明并返回Promise对象
return new Promise((resolve, reject) => {
// 创建 img 实例
const img = new Image();
// 当图片加载成功时
img.addEventListener('load', () => {
// 返回这个img实例
resolve(img);
});
// 当图片加载失败时
img.addEventListener('error', () => {
reject(new Error(`Cloud not load img at ${url}`));
});
// 当img实例的src被赋值时开始加载图片
img.src = url;
});
};

// 获取imgDom元素
const imgDom = document.querySelector('img');

// 调用异步加载的函数
loadImgAsync('https://imgblog.yubai.eu.org/绿灯.png')
.then(img => {
// 加载成功后替换src
imgDom.src = img.src;
// console.log(img);
})
.catch(err => {
// 加载失败打印错误信息
console.log(err);
});
</script>
</body>
</html>

💡async/await

🐼1.什么是async/await

async/await是基于Promise实现的

async/await使得异步代码看起来像同步代码

以前的方法有回调函数和Promise,async/await是写异步代码的新方式

🐼2.async/await语法

async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,在接着执行函数体内后面的语句。示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const timeout= time => {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve();
}, time);
})
};
const start = async () => {
// 在这里使用起来就像同步代码那样直观
console.log('start');
await timeout(3000);
console.log('end');
return "imooc"
};
start().then(()=> {
console.log('test....');
});

// 输出结果:
// 'start'
// 等待3s后
// 'end'
// 'test....'

🐼3.注意事项

1、await 命令只能在 async 函数之中,如果用在普通函数,就会报错!

2、await 后面跟着是一个 Promise 对象,会等待 Promise 返回结果了,再继续执行后面的代码

3、await 后面跟着的是一个数值或者字符串等数据类型的值,则直接返回该值

4、await后面跟着的是定时器,不会等待定时器里面的代码执行完,而是直接执行后面的代码,然后再执行定时器中的代码

🐼4.错误捕获

可以直接用标准的 try catch的语法捕获错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const timeout = time => {
return new Promise(function (resolve, reject) {
setTimeout(function () {
// 模拟出错了,返回 ‘error’
reject('error');
}, time);
})
};
const start = async () =>{
try {
console.log('start');
await timeout(3000); // 这里得到了一个返回错误

// 所以以下代码不会被执行了
console.log('end');
} catch (err) {
console.log(err); // 这里捕捉到错误 `error`
}
};
start()

⭕ES6 —— Class类

🔔Class 是什么?

🐼1.概念

类可以看做是对象的模板,用一个类可以创建出许多不同的对象

🐼2.基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 类名一般首字母大写
class Person {
//实例化时执行构造方法,所以必须有构造方法,但可以不写出来
constructor(name, age){
this.name = name;
this.age = age;

// 一般在构造方法中定义属性,方分不在构造方法中定义
// this.speak = () =>{}
}
// 方法只能在这里定义
// 各个实例共享的方法
speak(){
console.log('speak');
}
}

const p = new Person('zhangsan', 19);

🔔Class的两种定义形式

🐼1.声明形式

1
2
3
4
5
class Person {
constructor(){}
speak(){}
}
new Person();

🐼2.表达式形式

1
2
3
4
5
6
7
8
9
const Person = class{
constructor(){}
speak(){}
}
// 立即执行的类
new (class{
constructor(){}
speak(){}
})();

🔔实例属性、静态方法、静态属性

🐼1.实例属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Person {
// 实例属性:直接写在构造函数外面,就是相当于给实例一个默认的属性。
age = 18;
sex = 'male';
// 实例方法也是属性
getGender = function() {
console.log(this.name);
};
constructor(name, sex) {
this.name = name;
// 在实例化类的时候,参数会覆盖默认的属性
this.sex = sex;
}
}
const p = new Person('zhangsan', 'woman');
console.log(p.name); // 'zhangsan'
console.log(p.age); // 18
console.log(p.sex); // 'woman'
p.getGender(); // 'zhangsan'

🐼2.静态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Person {
constructor() {}
speak() {
console.log('speak');
// this 指向实例
console.log(this);
}
// 使用 static 标识符声明静态方法
static speak() {
console.log('static speak');
// this 指向类
console.log(this);
}
}
// 不推荐,不利于封装!
// Person.speak = function() {
// console.log('speak');
// };
const p = new Person();
p.speak(); // 'speak'
Person.speak(); // 'static speak'

🐼3.静态属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person {
constructor(name) {
this.name = name;
}

// 使用静态方法的形式获取
static getVision() {
return '1.0';
}

// 不要这么写,目前只是提案,有兼容性问题
// static vision = '1.0'
}
// Person.vision = '1.0';
const p = new Person('zhangsan');
// console.log(Person.vision);
console.log(Person.getVision());

🔔私有属性和方法

🐼1.为什么需要私有属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
// 一般情况下,类的属性和方法都是公开的
// 公有属性和方法可以被外界修改,造成意想不到的错误
class Person {
constructor(name) {
this.name = name;
}
speak() {
console.log('speak');
}
}
const p = new Person('zhangsan');
console.log(p.name); // 'zhangsan'
p.speak(); // 'speak'

🐼2.模拟私有属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 2.1_开头表示私有
class Person {
constructor(name) {
this._name = name;
}
speak() {
console.log('speak');
}

getName() {
return this._name;
}
}
const p = new Person('zhangsan');
console.log(p.name);
console.log(p.getName());
p.speak();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 2.2.将私有属性和方法从类中移出去
// 一般是在模块中,演示使用立即执行函数模拟
(function () {
let name;
class Person {
constructor(username) {
// this.name = username;
name = username;
}
getName() {
return name;
}
}

window.Person = Person;
})();
(function () {
const p = new Person('zhangsan');
console.log(p.name); // undefined
console.log(p.getName()); // 'zhangsan'
})();

🔔继承 —— extends

🐼1.子类继承父类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 父类
class Person {
constructor(name, age) {
this.name = name;
this.age = age;

this.say = function () {
console.log('say');
};
}
speak() {
console.log('speak');
}
static speak() {
console.log('static speak');
}
}
Person.vision = '1.0';

// 子类
class Student extends Person {
constructor(name, age) {
super(name, age);
}
}

// 实例化子类对象
const stu = new Student('zhangsan', 19);
console.log(stu.name); // zhangsan
console.log(stu.age); // 19
stu.say(); // say
stu.speak(); // speak
Student.speak(); // static speak
console.log(Student.vision); // 1.0

🐼2.改写继承的属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class Person {
constructor(name, age) {
this.name = name;
this.age = age;

this.say = function () {
console.log('say');
};
}
speak() {
console.log('speak');
}
static speak() {
console.log('static speak');
}
}
Person.vision = '1.0';

class Student extends Person {
constructor(name, age, gender) {
super(name, age);

this.gender = gender;
}
// 同名覆盖 —— 改写属性和方法
speak() {
console.log('Student speak');
}
static speak() {
console.log('static Student speak');
}
hello() {
console.log('hello');
}
}

const stu = new Student('zhangsan', 19, '男');
console.log(stu.name); // zhangsan
console.log(stu.age); // 19
console.log(stu.gender); // '男'
stu.say(); // say
stu.speak(); // speak
stu.hello(); // hello
Student.speak(); // static speak
console.log(Student.vision); // 1.0

🔔继承 —— super

🐼1.作为函数调用

代表父类的构造方法,只能用在子类的构造方法中,用在其他地方就会报错

super 虽然代表了父类的构造方法,但是内部的 this 指向子类的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Person {
constructor(name, age) {
this.name = name;
this.age = age;

console.log(this);
}
speak() {
console.log('speak');
}
}

class Student extends Person {
constructor(name, age) {
super(name, age); // super() 中的 this 指向了子类的实例
}
}

new Student('zhangsan', 19);

🐼2.作为对象使用

📍2.1 在构造方法中使用或一般方法中使用

super 代表父类的原型对象 父类.prototype

所以定义在父类实例上的方法和属性,是无法通过 super 调用的

通过 super 调用父类的方法,方法内部的 this 指向当前的子类实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
speak() {
console.log('speak');
console.log(this);
}
}

class Student extends Person {
constructor(name, age) {
super(name, age);

// 子类的构造方法中 super 代表了父类.prototype
console.log(super.name);
super.speak();
}
}

new Student('zhangsan', 19);
📍2.在静态方法中使用

super 指向父类,而不是父类的原型对象

通过 super 调用父类的方法时,方法内部的 this 指向当前的子类,而不是子类的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 父类中添加静态方法
static speak() {
console.log('Person speak');
console.log('this');
}
// 子类中添加静态方法
static speak(){
// 调用父类的静态方法
super.speak(); // 内部的 this 指向了子类
console.log('Student speak');
}
// 调用子类静态方法
Student.speak();
// 控制台输出:
// Person speak
// Student speak

🐼3.注意事项

使用 super 的时候,必须显式指定是作为函数还是作为对象使用,否则会报错