ES6补档

数据结构 Set 和 Map

整体概述:

Set是一种类似数组的数据结构,区别在于其存储的成员都是不重复的,由此带来了它的一个应用就是:去重。Set通过new关键字实例化,入参可以是数组 or 类数组的对象。

值得注意的是:在Set中,只能存储一个NaN,这说明在 Set 数据结构中,NaN等于NaN

Set实例的方法:
操作方法 add()、delete()、has()和 clear();
遍历方法:keys()、values()、entries()和 forEach();扩展运算符…、
数组方法 map()、filter()方法也可以用于 Set 结构。
由此它可以很方便的实现数组的交、并、差集。

WeakSet类似于 Set,主要区别在于 1.成员只能是对象类型; 2.对象都是弱引用(如果其他对象都不再引用该对象,垃圾回收机制会自动回收该对象所占的内存,不可预测何时会发生,故 WeakSet 不可被遍历)

JavaScript 对象 Object 都是键值 K-V 对的集合,但 K 取值只能是字符串和 Symbol,Map 也是 K-V 的集合,然而其 K 可以取任意类型。
如果需要键值对的集合,Map 比 Object 更适合。Map 通过 new 关键字实例化。

Map实例的方法:set()、get()、has()、delete()和 clear();
遍历方法同 Set。

Map与其它数据结构的互相转换:Map <—> 数组| Map <—> 对象| Map <—> JSON。

WeakMap类似于Map,主要区别在于: 1.只接受对象作为键名; 2.键名所指向的对象不计入垃圾回收机制。

Set

Set 是一个类似数组,但是与数组不同,它具有唯一性.里面的元素都是不重复的,本身是一个构造函数.

1
2
3
4
5
6
7
8
9
10
11
const SetArr = new Set();

[1,1,2,2,3,3].forEach(item=>SetArr.add(item));
console.log(SetArr);

=>
0: 1
1: 2
2: 3
size: 3
__proto__: Set

实例属性

  • constructor: 构造函数,就是 Set 函数
  • size: 返回的是 Set 实例的长度

实例方法

  • add(): 往 Set 里添加值,返回 Set 本身
  • delete(): 删除某个值,返回布尔值判断是否成功
  • has(value): 判断是否有 value,返回布尔值
  • clear(): 清除 Set 中所有值,无返回值

遍历方法

  • forEach(): 使用回调函数遍历元素
  • entries(): 返回键值对的遍历器,用于遍历[键名,键值]组成的数组
  • values(): 返回键值遍历器,用于遍历所有键值
  • keys(): 返回键名遍历器,用于遍历所有键名

    由于 Set 结构是只有键值的结构,所有 keys 方法与 values 方法返回一致。

forEach

1
2
3
let s = new Set([1, 2, 3, 4, 5]);
s.forEach((item) => console.log(item + 1));
//2 3 4 5 6

entries

1
2
3
4
5
6
7
let s = new Set(["a", "b", "c"]);
for (let item of s.entries()) {
console.log(item);
}
//(2) ["a", "a"]
//(2) ["b", "b"]
//(2) ["c", "c"]

values

1
2
3
4
5
let s = new Set(["a", "b", "c"]);
for (let item of s.values()) {
console.log(item);
}
//a b c

keys

1
2
3
4
5
let s = new Set(["a", "b", "c"]);
for (let item of s.keys()) {
console.log(item);
}
//a b c

使用 set 实现交并差

1
2
3
4
5
6
7
8
9
let a = new Set([1, 2, 3, 4]);
let b = new Set([3, 4, 5, 6]);

//交集
let value = new Set([...a].filter((item) => b.has(item)));
//并集
let value2 = new Set([...a], [...b]);
//差集
let value3 = new Set([...a].filter((item) => !b.has(item)));

WeakSet

一个弱的 Set 结构,弱的体现在WeakSet的成员只能是对象.
WeakSet的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用.
WeakSet没有 size 属性,无法遍历.只有实例方法:

  • add(): 向WeakSet添加新成员
  • delete(): 删除WeakSet的成员
  • has(): 判断WeakSet是否包含某个元素

Map

Set 结构是没有键只有值,而 Map 结构是键值的组合.也就可以使用各种数据类型作为键(包括对象).

1
2
3
4
5
let map = new Map();
let obj = { name: "alan", age: 20 };
map.set(obj, "这是一些描述");
map.get(obj);
console.log(map);

实例属性

  • constructor: Map 构造函数
  • size: 返回的是 Map 元素的长度

实例方法

  • set(key, value): 设置 key 所对应的 value 值
  • get(key): 获取 key 所对应的 value 值,若无则返回 undefined
  • delete(): 删除某个值,返回布尔值判断是否成功
  • has(value): 判断是否有 value,返回布尔值
  • clear(): 清除 Set 中所有值,无返回值

遍历方法

  • forEach(): 使用回调函数遍历元素
  • entries(): 返回键值对的遍历器,用于遍历[键名,键值]组成的数组
  • values(): 返回键值遍历器,用于遍历所有键值
  • keys(): 返回键名遍历器,用于遍历所有键名

keys

1
2
3
4
5
6
7
8
9
let person = new Map([
["name", "alan"],
["age", "20"],
]);
for (let item of person.keys()) {
console.log(item);
}
//name
//age

values

1
2
3
4
5
6
7
8
9
let person = new Map([
["name", "alan"],
["age", "20"],
]);
for (let item of person.values()) {
console.log(item);
}
//alan
//20

与其他数据结构转换

  • Map 转数组
1
2
3
4
5
let person = new Map([
["name", "alan"],
["age", "20"],
]);
console.log(...person);
  • 数组转 Map
1
2
3
4
5
let arr = [
["name", "alan"],
["age", "20"],
];
let m = new Map(arr);
  • Map 转对象
1
2
3
4
5
6
7
8
9
10
11
12
13
let person = new Map([
["name", "alan"],
["age", "20"],
]);
function swap(map) {
let obj = Object.create(null);
for (let [key, value] of map) {
obj[key] = value;
}
return obj;
}
swap(person);
//{name: "allan", age: "20"}
  • 对象转 Map
1
2
3
4
5
6
7
8
let person = { name: "allen", age: "20" };
function swap(obj) {
for (let key of Object.keys(obj)) {
map.set(key, obj[key]);
}
return map;
}
swap(person);

WeakMap

WeakMap 与 Map 一样可以生成键值对的集合,但是也有不同的地方,主要是有以下的两点:

  • WeakMap 只接受对象作为键名(不包括 null)
  • ct

与 WeakSet 相似,WeakMap 也是没有遍历的操作,也没有 size 属性,没有办法列出所有键名(由于垃圾回收机制的运行),也不能清空。

实例方法

  • get():
  • set():
  • delete(): 删除 WeakMap 的成员
  • has(): 判断 WeakMap 是否包含某个元素

Array.from

将类数组对象转化为数组.
第二个参数可以对传入值进行处理.

模块化

方式有立即执行函数,AMD,CMD,CommonJS,ES6 模块化.
其中最常用的是 CommonJS 和 ES6 模块化

区别

  • CommonJS 即使用require(),module.exports,通常在 Node 中使用.支持动态导入.属于同步导入.
  • ES6 模块化即使用import,export default.通常用于浏览器,属于异步导入.
  • CommonJS 在导出时是值拷贝,就算导出的值变了,导入的也不会变.如果想更新,必须重新导入.ES6 模块化采用实时绑定,导出导入都指向同一个地址,所以导入变化导出也会变化.
  • ES6 模块会编译成require/exports来执行.
1
2
3
4
5
6
7
8
9
10
11
12
13
// AMD
define(["./a", "./b"], function (a, b) {
// 加载模块完毕可以使用
a.do();
b.do();
});
// CMD
define(function (require, exports, module) {
// 加载模块
// 可以把 require 写在函数体的任意地方实现延迟加载
var a = require("./a");
a.doSomething();
});

Commonjs

1
2
3
4
5
6
7
8
9
10
// a.js
module.exports = {
a: 1,
};
// or
exports.a = 1;

// b.js
var module = require("./a.js");
module.a; // -> log 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var module = require("./a.js");
module.a;
// 这里其实就是包装了一层立即执行函数,这样就不会污染全局变量了,
// 重要的是 module 这里,module 是 Node 独有的一个变量
module.exports = {
a: 1,
};
// module 基本实现
var module = {
id: "xxxx", // 我总得知道怎么去找到他吧
exports: {}, // exports 就是个空对象
};
// 这个是为什么 exports 和 module.exports 用法相似的原因
var exports = module.exports;
var load = function (module) {
// 导出的东西
var a = 1;
module.exports = a;
return module.exports;
};
// 然后当我 require 的时候去找到独特的
// id,然后将要使用的东西用立即执行函数包装下,over

另外虽然 exports 和 module.exports 用法相似,但是不能对 exports 直接赋值。因为 var exports = module.exports 这句代码表明了 exports 和 module.exports 享有相同地址,通过改变对象的属性值会对两者都起效,但是如果直接对 exports 赋值就会导致两者不再指向同一个内存地址,修改并不会对 module.exports 起效。

箭头函数

  1. 箭头函数继承而来的 this 指向永远不变.

使用call,apply,bind都无法改变箭头函数 this 指向.

  1. 箭头函数不能作为构造函数使用.因为它没有自己的 this
  2. 箭头函数没有自己的arguments对象.

在箭头函数中访问 arguments 实际上获得的是外层局部(函数)执行环境中的值.
如果想访问箭头函数中的参数,可以使用rest参数.

  1. 箭头函数没有原型prototype
  2. 箭头函数不能用作Generator函数,不能使用 yeild 关键字

类和继承

ES6 的类,完全可以看作构造函数的另一种写法。
类的数据类型就是函数,类本身就指向构造函数。

1
2
3
4
5
6
class Point {
// ...
}

typeof Point; // "function"
Point === Point.prototype.constructor; // true

类的所有方法都定义在类的 prototype 属性上面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Point {
constructor() {
// ...
}
toString() {
// ...
}
toValue() {
// ...
}
}
// 等同于
Point.prototype = {
constructor() {},
toString() {},
toValue() {},
};

constructor 关键字

constructor()方法就是构造方法,而this关键字则代表实例对象。
constructor()方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有 constructor()方法,如果没有显式定义,一个空的constructor()`方法会被默认添加。

extends 关键字

作用:类继承
底层原理: 替换原型继承(不会覆盖子构造函数的原型)
extends 关键字替换原型的原型:s1.__proto__.__proto__ = Person.prototype

super 关键字

用于调用父类的方法.
子类中如果想使用constructor,必须使用super关键字调用父类的方法.
super这个关键字,既可以当作函数使用,也可以当作对象使用

在子类的构造函数中,只有调用 super()之后,才可以使用 this 关键字,否则会报错。这是因为子类实例的构建,必须先完成父类的继承,只有 super()方法才能让子类实例继承父类。
如果子类没有定义 constructor()方法,这个方法会默认添加,并且里面会调用 super()。也就是说,不管有没有显式定义,任何一个子类都有 constructor()方法。

当做函数

1
2
3
4
5
6
7
class A {}

class B extends A {
constructor() {
super();
}
}

注意,super 虽然代表了父类 A 的构造函数,但是返回的是子类 B 的实例,即 super 内部的 this 指的是 B 的实例,因此 super()在这里相当于 A.prototype.constructor.call(this)。

当做对象

super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。

1
2
3
4
5
6
7
8
9
10
11
12
13
class A {
p() {
return 2;
}
}
class B extends A {
constructor() {
super();
console.log(super.p()); // 2
}
}

let b = new B();

上面代码中,子类 B 当中的 super.p(),就是将 super 当作一个对象使用。这时,super 在普通方法之中,指向 A.prototype,所以 super.p()就相当于 A.prototype.p()。

注意,由于 super 指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过 super 调用的。

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
class Person {
// private 私有,仅在类内使用
private name:string = 'Tom';
// public 公共,内外都可以使用
public age: number = 10;
// protected 可在类内和继承的子类中调用
protected sex: string;
getName() {
return 'Name'
}
//constructor中的会在new一个实例时调用,接收传入的参数
constructor(name: string) {
this.name = name
}
}

const person = new Person('dell');//此时调用constructor中的方法
console.log(person.name);

class Teacher extends Person {
getTeacherName() {
return 'teacher'
}
getName(){
//super关键字表示父类
return super.getName()
}
constructor(age: number){
//子类中的constructor要想使用,需要调用父类的constructor
//super()就表示父类的constructor
super('dell');
}
}
const person = new Teacher(123);//此时调用constructor中的方法
console.log(person.name); // 子类继承了父类,并且super()传入'dell',则打印dell
console.log(person.age); // 子类继承了父类,虽然传参123,
// 但是子类的构造函数内没有对应的this赋值给age,则继承父类的age,父类的原型对象上age是10

类中的 getter 和 setter

私有属性无法在外部调用,可以利用getter间接调用私有属性.使用setter间接设置私有属性.

1
2
3
4
5
6
7
8
9
10
11
class Person {
constructor(private _name: string) {}
get name() {
return this._name;
}
set name(name: string) {
this._name = name;
}
}
const person = new Person("dell");
person.name = "dell";

单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//static表示挂载在类上,而不是类的实例上
class Demo {
private static instance: Demo;
private constructor() {}

static getInstance() {
if (!instance) {
this.instance = new Demo();
}
return this.instance;
}
}

const demo1 = Demo.getInstance();
const demo2 = Demo.getInstance();

抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 使用abstract 关键字抽象类,将公共方法属性抽离出来
abstruct class Geom {
width: number;
getType() {
return 'Geom'
}
// 这里写abstract的话,子类中必须声明此方法
abstract getArea(): number;
}

class Circle extends Geom {
getArea() {
return '123'
}

Proxy

Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作。
Proxy 内置一系列陷阱,用于创建一个对象的代理,从而实现基本操作的拦截和自定义.

简单来说, 通过Proxy创建对于原始对象的代理对象,从而在代理对象中使用Reflect达到对JS操作的拦截.
1
let p = new Proxy(target, handler)
target 代表需要添加代理的对象,handler 用来自定义对象中的操作,比如可以用来自定义 set 或者 get 函数。
1
2
3
4
5
6
7
8
9
10
11
const obj = {
name: 'Tom'
}
const proxy = new Proxy(obj, {
get(target, key, receiver) {
console.log('劫持的对象名', target)
console.log('劫持的属性名', key)
return target[key]
}
})
proxy.name // "Tom"
上面就是通过Proxy创建了一个代理对象,同时在Proxy内部声明了一个get陷阱. 当我们访问`proxy.name`时实际触发了对应的get陷阱.执行陷阱中的逻辑. `target`就是劫持的源对象`obj`, `key`就是劫持的获取的name值,注意getter函数取值时才会触发. `receiver`表示代理的对象proxy或者继承Proxy的对象.也就是谁调用了函数,就是指的谁.
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
const parent = {
name: 'parent',
get value() {
return this.name;
},
};

const handler = {
get(target, key, receiver) {
return Reflect.get(target, key);
// 这里相当于 return target[key]
},
};

const proxy = new Proxy(parent, handler);

const obj = {
name: 'obj',
};

// 设置 obj 继承与 parent 的代理对象 proxy
Object.setPrototypeOf(obj, proxy);

console.log(obj.value); // parent
// 这里因为返回的是源对象的 value,所以是父级的,想要是继承的,需要加上 receiver

const handler = {
get(target, key, receiver) {
return Reflect.get(target, key, receiver);
// 这里相当于 return target[key].call(receiver)
},
};

````

- Proxy中接受的Receiver形参表示代理对象本身或者继承自代理对象的对象
- Reflect中传递的Receiver实参表示修改执行原始操作时的this指向.(实际上就是指向调用者).

接下来我们通过 Proxy 来实现一个数据响应式
```javascript
let onWatch = (obj, setBind, getLogger) => {
let handler = {
get(target, property, receiver) {
getLogger(target, property)
return Reflect.get(target, property, receiver)
},
set(target, property, value, receiver) {
setBind(value, property)
return Reflect.set(target, property, value, receiver)
}
}
return new Proxy(obj, handler)
}

let obj = { a: 1 }
let p = onWatch(
obj,
(v, property) => {
console.log(`监听到属性${property}改变为${v}`)
},
(target, property) => {
console.log(`'${property}' = ${target[property]}`)
}
)
p.a = 2 // 监听到属性a改变
p.a // 'a' = 2
````

在上述代码中,我们通过自定义 set 和 get 函数的方式,在原本的逻辑中插入了我们的函数逻辑,实现了在对对象任何属性进行读写时发出通知。

当然这是简单版的响应式实现,如果需要实现一个 Vue 中的响应式,需要我们在 get 中收集依赖,在 set 派发更新,之所以 Vue3.0 要使用 Proxy 替换原本的 API 原因在于 Proxy 无需一层层递归为每个属性添加代理,一次即可完成以上操作,性能上更好,并且原本的实现有一些数据更新不能监听到,但是 Proxy 可以完美监听到任何方式的数据改变,唯一缺陷可能就是浏览器的兼容性不好了。

# Reflect

Reflect 提供拦截 JS 操作的方法.并非一个构造函数,不能用 new 进行调用.
它的所有方法和属性都是静态的.

```javascript
Reflect.get(target, key, receiver);
// 也就是Reflect将get操作转发给target,获取key的值,相当于target[key]

虽然也可以直接使用target[key],但是 Reflect 可以用 receiver,更强大.

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 实现功能
let array = [1, 2, 3];

array[-1]; // 3,最后一个元素
array[-2]; // 2,从末尾开始向前移动一步
array[-3]; // 1,从末尾开始向前移动两步

array = new Proxy(array, {
get(target, key) {
// key 是字符串
key = Number(key);
if (Number(key) < 0) {
key = key + target.length;
}
return Reflect.get(target, key);
},
});

深层遍历

1
2
3
4
5
6
7
8
9
const handler = {
get(target, key){
let res = Reflect.get(target.key)
retrun typeof res === 'object' ? new Proxy(res,handler) : res
},
set(target, key, val){
return Reflect.set(target, key, val)
}
}

Generator

Generator 即生成器,它会返回一个迭代器.Generator 最大的特点就是可以控制函数的执行。可以作为异步解决方案.

1
2
3
4
5
6
7
8
9
function* foo(x) {
let y = 2 * (yield x + 1);
let z = yield y / 3;
return x + y + z;
}
let it = foo(5);
console.log(it.next()); // => {value: 6, done: false}
console.log(it.next(12)); // => {value: 8, done: false}
console.log(it.next(13)); // => {value: 42, done: true}

Generator 函数需要手动调用next执行,在遇到yield会暂停,返回一个对象,包括执行结果和是否执行完毕.继续调用next会从之前暂停的地方开始,如果传入参数,会覆盖之前的结果.到下一个yield结束.
分析上面的代码:

  • 第一次执行next,参数是 5,遇到yield暂停,返回值为 5+1 => 6.
  • 第二次执行next,next有参数 12, 覆盖上一次yield处的值,就是y = 2*12,要在这一次的yield处暂停就要 24/3,得到 8
  • 最后一次执行next,z 就被赋值为 13,结合第二次 y 是 24,第一次 x 是 5,最后返回 5+13+24=42.

Promise

Promise 是异步问题同步化解决方案.
Promise 本身不是异步,它是个构造函数.
Promise 中的函数是同步的.
Promise 有一个参数,即excutor执行器,它有两个参数,resolvereject.
excutor是同步执行,而then是异步调用的.
如果想连续then需要上一个promise中 return 一个新的promise.
Promise 对象的错误具有冒泡性质,会一直向后传递,直到被捕获为止.

状态

promise 内部在resolve之前的代码处于pending状态,之后的是fulfilled.
promise 在catch中如果没有抛出错误,则会返回fufilled

链式调用

状态固化后,就不再捕获错误了.比如 resolve()返回后面有报错的内容,但是 catch 就不再捕获了.
案例:
当 promise 作为参数传递到另一个 promise 中,这个第二个 promise 中的状态就会失效.依赖于第一个 promise.

1
2
3
4
5
6
7
8
const p1 = new Promise((reslove,reject)=>{
setTimeout(()=>{reject(new Error('fail')},3000)
})
const p2 = new Promise((resolve,reject)=>{
setTimeout(()=>{resolve(p1)},1000)
})
p2.then(result=>console.log(result))
.catch(err=>console.log(err))

Promise.all()如果三个内容都失败,只返回第一个失败的信息.如果只有一个出错,也只会返回一个.而且不返回正常的.
Promise.race()是谁先完成返回谁,无论是成功还是失败.

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
promise
.then((value) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject("Error");
}, 300);
});
})
.then(
(value) => {
console.log(value);
},
(reason) => {
console.log(reason);
} // 'Error'
// 默认return 的是undefined
)
.then(
//上一次调用只有失败的状态,而那里默认返回的是undefined,
//这里在onFulfilled的这里捕获的是上一次失败的默认return的undefined
(value) => {
console.log(value);
}, // 'undefined'
(reason) => {
console.log(reason);
}
);
1
2
3
4
5
6
7
8
9
10
// then中throw new Error()的情况
.then(
(value)=>{throw new Error('Error')
)
.then(
// 上面抛出的是error,所以要走onRejectd,也就是第二个函数
(value)=>{console.log(value)},
(reason)=>{console.log(reason)} // 'Error'
)

缺点

其实它也是存在一些缺点的,比如无法取消 Promise,错误需要通过回调函数捕获。

如何中断 promise

插入一个 pending 状态的 promise.

catch

catch 在 promise 的源码层面就是一个 then,如果 catch 中 return 的有值,可以在 catch 后跟 then.
既有 then 中的失败回调,又有 catch 的情况,会走最近的那个.

1
2
3
4
5
6
7
8
9
10
11
12
13
const p1 = Promise.resolve();
p1.then(() => {
console.log(1);
throw new Error();
})
.catch(() => {
console.log(2);
})
.then(() => {
console.log(3);
});
// 输出1,2,3
// catch后没有抛出错,则返回的是resolved状态,可以继续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
function loadIMg(src) {
return new Promise((resolve, reject) => {
const img = document.createElement("img");
img.onload = function () {
resolve(img);
};
img.onerror = function () {
reject(new Error("加载失败"));
};
img.src = src;
});
}
const url1 =
"https://i1.hdslb.com/bfs/face/f4d60f852eb1a85696447838c90a94acad31b7ae.jpg@160w_160h_1c_1s.webp";
const url2 =
"https://i0.hdslb.com/bfs/face/a8ef30d6688d0b532bd20baa160417deae8f386d.jpg@240w_240h_1c_1s.webp";
// 连续加载图片
loadImg(url1)
.then((img) => {
console.log(img);
return loadImg(url2);
})
.then((img2) => {
console.log(img2);
});

语法糖

Promise.resolve()
Promise.reject()

Promise.all

如果所有都正确执行,会按顺序返回.有一个失败,就失败.

Async/await

一个函数前加了async,它就会返回 Promise.它的返回值会被Promise.resolve()包裹.

await只能配套async使用

缺点

因为 await 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 await 会导致性能上的降低。
如果代码没有依赖性的话,完全可以使用 Promise.all 的方式

async/await 和 promise 的关系

await 是等待 promise 的返回,await 做不了 promise 返回状态的工作.
await 对应Promise.then成功的情况.
await 后如果是函数,则会把函数返回的结果用Promise.then()返回.
async 中的Promise.reject()需要使用try/catch进行捕获错误.
async 中的函数内部属于同步代码,在遇到 await 之前都可以先执行.

1
2
3
4
5
6
7
8
9
let a = 0;
let b = async () => {
a = a + (await 10);
console.log("2", a); // -> '2' 10
};
b();
a++;
console.log("1", a); // -> '1' 1
// 先得到'1' 1, 后得到'2' 10
  • 首先函数 b 先执行,在执行到 await 10 之前变量 a 还是 0,因为 await 内部实现了 generatorgenerator 会保留堆栈中东西,所以这时候 a = 0 被保存了下来
  • 因为 await 是异步操作,后来的表达式不返回 Promise 的话,就会包装成 Promise.reslove(返回值),然后会去执行函数外的同步代码
  • 同步代码执行完毕后开始执行异步代码,将保存下来的值拿出来使用,这时候 a = 0 + 10

上述解释中提到了 await 内部实现了 generator,其实 await 就是 generator 加上 Promise 的语法糖,且内部实现了自动执行 generator。