es6
ES6
1. let和const
var是传统上的函数作用域。ES6推荐使用let声明局部变量,const声明常量,两者都为块级作用域。
let用法1
2
3
4
5
6
7
8
9var name = 'andy';
{
// let声明局部变量
let name = 'qiqi';
console.log(name); // qiqi
}
console.log(name); // andyconst用法1
2
3
4
5const name = 'andy';
name = 'qiqi'; // 报错,不可被修改
const person = { name: 'andy' };
person.name = 'qiqi'; // 可以被修改
由const声明的变量都会被认为是常量,即它的值被设置完成后就不能再修改了,但是如果const是一个对象,对象所包含的值是可以被修改的。抽象点说,就是对象所指向的地址没有变。
以下几点需要注意:
let关键字声明的变量不具备变量提升特性;let和const声明只在最靠近的一个块中有效;const在声明时必须被赋值
2. 函数相关
2.1 箭头函数
ES6中,箭头函数是函数的一种简写形式。使用括号包裹参数,跟随一个 =>,紧接着是函数体;
箭头函数的三个特点:
- 不需要
function关键字来创建函数 - 可以省略
return关键字 - 没有自己的
this,继承当前上下文的this 
1  | [1, 2, 3].map(x => x + 1);  | 
2.2 函数默认值
在参数括号内直接设置默认值
1  | function Person(name = 'andy', age = 12) {  | 
2.3 Spread/Rest操作符
1  | // restParams代表剩下所有的参数  | 
3. 模板字符串
在ES6之前我们用+来拼接字符串,但是在ES6中,可以使用``反引号来使用模板字符串
1  | // ES5  | 
4. 对象和数组的解构
对象解构
1
2
3
4
5
6
7
8const person = {
name: 'andy',
age: 11,
}
const { name, age } = person;
console.log(name); // andy;
console.log(age): // 12;数组解构
1
2
3
4const persons = ['andy', 'qiqi'];
const [boy, girl] = persons;
console.log(boy); // andy;
console.log(girl); // qiqi;
5. for...in和for...of
for...in:用来遍历对象中的属性1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 对象的遍历
const person = {
name: 'andy',
age: 12
}
for(let key in person) {
console.log(key); // name, age
}
// 数组的遍历
const ages = [12, 13];
for(let key in ages) {
console.log(key); // 0, 1
}for...of:用来遍历一个迭代器1
2
3
4
5
6// 数组的遍历
const ages = [12, 13];
for(let key of ages) {
console.log(key); // 12, 13
}for...of不能用来遍历对象,这是因为ES6中引入了Iterator,只有提供了Iterator接口的数据类型才可以使用for...of来循环遍历,而Array,Set,Map,某些类数组默认都提供了Iterator接口,所以它们可以使用for...of来进行遍历。
6. class
6.1 语法糖
ES6中支持class语法,不过,ES6的class不是新的对象继承模型,它只是原型链的语法糖表现形式,它的绝大部分功能,ES5都可以做到。新的class写法只是让对象原型的写法更加清晰,更像面向对象编程的语法而已。
1  | class Person {  | 
构造函数的prototype属性,在ES6的类上面继续存在。事实上,类的所有方法都定义在类的prototype上。
6.2 属性不可枚举
另外,类内部所有定义的方法,都是不可枚举的,这点与ES5不一样,如果是以ES5定义的构造函数,则其内部的属性都是可枚举的:
1  | class Person {  | 
6.3 constructor方法
constructor()方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,系统会自动加上一个默认的constructor方法。
constructor方法默认返回实例对象this,也可以指定返回另外一个对象。
1  | class Person {  | 
6.4 原型
与ES5一样,实例的属性除非显示定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。
1  | class Person {  | 
__proto__并不是语言本身的特性,这是各大厂商具体实现时添加的私有属性,目前虽然在很多现代浏览器的JS引擎中都提供了这个私有属性,但依旧不建议在生产中使用该属性,避免对环境产生依赖。生产环境中,可以使用getPrototypeOf方法来获取实例对象的原型,然后再来为原型添加方法/属性。
6.5 取值函数(getter)与存值函数(setter)
与ES5一样,在类内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
1  | class Person {  | 
6.7 属性表达式
类的属性名,可以采用表达式:
1  | const action = 'run';  | 
6.8 静态方法
static用来定义类的静态方法,类的静态方法不会被实例所继承,只能通过类直接调用。如果静态方法中有this,则此this指向这个类,而不是其实例,
子类可以继承父类的静态方法。
静态方法可以与非静态方法重名。
6.9 类的继承
Class可以通过extends关键字实现继承,这比ES5通过修改原型链实现继承要清晰和方便。
子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的梳理属性和方法。如果不调用super方法,子类就得不到 this对象。
如果super作为对象,用在静态方法之中,这是super将指向父类,而不是父类的原型对象。
有几点值得注意:
- 类内部默认就是严格模式,所以不需要使用
use strict指定运行模式; - 类的声明不会提升,如果要使用某个
class,必须在使用之前定义它,否则会报错; - 在类中定义函数不需要使用
function关键字; - 类可以通过
extends继承一个父类,但是子类的constructor中需要执行super()函数; - 类必须使用
new关键字创建实例,不可直接执行,这点与ES5的构造函数不同; 
7. module
在ES6之前,JavaScript一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。不过社区指定了一些模块加载方案,最主要的有CommonJS和AMD两种,前者用于服务器,后者用于浏览器。在ES6中提供了一个标准的模块加载方案,主要通过export和import实现:
7.1 加载时机(编译时or运行时)
7.1.1 运行时加载
CommonJS和AMD模块,都只能在运行时确定模块的依赖关系。比如,CommonJS模块就是对象,输入时必须查找对象属性:
1  | // CommonJS模块  | 
上面代码实质是整体加载fs模块,生成一个对象_fs,然后再从这个对象上面读取3个方法。这种加载成为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”。
7.1.2 编译时加载
ES6模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。ES6模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入:
1  | // ES6模块  | 
上面代码的是实质是从fs模块加载两个方法,其他方法不加载。这种加载成为“编译时加载”,或者静态加载,即ES6可以在编译时就完成模块加载,效率要比CommonJS模块的加载方式高。当然,这也导致了没法引用ES6模块本身,因为它不是对象。
除了静态加载带来的各种好处,ES6模块还有以下好处:
- 不再需要
UMD模块格式了,将来服务器和浏览器都会支持ES6模块格式。目前,通过各种工具库,其实已经做到了这一点。 - 将来浏览器新
API就能用模块格式提供,不再必须做成全局变量或者navigator对象的属性。 - 不再需要对象作为命名空间,未来这些功能可以通过模块提供。
 
ES6的模块自动采用严格模式,不管你有没有在模块头部加上'use strict'
7.2 export
export命令主要用于规定模块的对外接口。
1  | // 方法1  | 
1  | // 方法2  | 
上面两种export的使用方法都是正确的,ES6都会将当前文件视为一个模块,里边使用export对外暴露了两个变量。从可读性上来说更推荐方法2的写法,因为这种写法把所有的输出同一放文件最后,能一目了然看到当前文件都输出了哪些变量。
不只是变量,export也可用于输出方法和类:
1  | function run() {  | 
1  | class Person {  | 
通常情况下,export输出的变量或者方法名就是该文件模块对外暴露的属性,但是也可以用as关键字进行重命名。
1  | function v1() {};  | 
另外,export输出的变量,与其对应的值是动态绑定关系,即通过该变量,可以获取到模块内部实时的值。这一点与CommonJS完全不同,CommonJS输出的是值的缓存,不存在动态更新。
最后,export模块只能放模块顶层,不能放入块级作用域或者方法中,否则会报错,这是因为处于条件代码块之中的export,没法做静态优化。
1  | function getName() {  | 
7.3 import
import命令主要用于引入其他模块提供的功能。使用export进行导出的接口,在其他文件中可以使用import进行引入。需要注意的是import引入的接口名必须与export导出的接口名一致,否则会报错,当然,也可以通过as关键字进行重命名:
1  | // 正常引入  | 
上面代码,通过import导入的age是一个属性接口,是只读的,如果在当前文件修改age的值是不被允许的,但是如果age是一个对象的话,是可以修改其属性的,不过需要注意的一点是,这里的修改会反应到person模块中,当其他模块引入了age这个接口后,也会获取更新后的值,所以这种做法是不被推荐的。除非你想做一些全局变量。
注意:import命令具有提升效果,会提升到整个模块的头部,首先执行:
1  | foo();  | 
上面的代码不会报错,因为import的执行早于foo。这种行为的本质是,import命令是编译阶段执行的,在代码之前。也正是因为import是静态执行,所以不能使用变量和表达式,这些只能在运行时才能拿得到结果的语法解构。
最后,import语句会执行所加载的模块,如下:
1  | import 'lodash';  | 
上面的代码仅仅执行lodash模块,但是不输入任何值。如果多次重复执行同义句import,那么只会执行一次,而不会执行多次,即import是一个单例模式(Singleton)。
7.4 模块的整体加载
除了指定某项输入值,还可以整体加载一个模块,要使用*指定一个对象,所有的输出值都会加载到这个对象上。
1  | import * as person from 'person';  | 
7.5 export default
export default用于为模块指定默认输出,
1  | export default function getName() {  | 
上面的代码未当前文件模块默认导出了一个方法getName,由此方法进行导出,引入的时候不用关心接口名,可以直接重命名:
1  | import getUserName from 'person';  | 
本质上,一个模块只能有一个export default,这也是为了通过export default导出的接口不需要使用大括号的原因。另一个本质是通过export default导出的接口默认接口名为default,即使是export default后边跟了方法名或者变量名,其与不跟的情况是一样的,所以在外部引入的时候可以随意重命名该接口。
一个模块文件只能有一个export default,但是同时可以存在多个export,所以引入时可以同时引入:
1  | export default React;  | 
1  | import React, { Component } from 'react';  | 
7.6 export与import结合
1  | // 方法1  | 
上面代码方法1中,export与import结合使用,需要注意的是,此种引入导出方法,getName与getAge并没有在此文件中引入,只是对外转发了这两个方法,所以此文件不能使用者两个方法。
- 模块的接口改名,可以采用下面方法:
 
1  | export { getAge as getInfo } from 'person';  | 
- 模块的整体输出:
 
1  | export * from 'person';  | 
- 默认接口的写法如下:
 
1  | export { default } from 'person';  | 
- 具体接口名改为默认接口:
 
1  | export { getName as default } from 'person';  | 
- 默认接口改为具体接口名:
 
1  | export { default as getName } from 'person';  | 
整体导出改为接口名:
在
ES2020之前,这种import语句,没有复合写法:
1  | import * as personMethod from 'person';  | 
ES2020补上了这种写法:
1  | export * as personMethod from 'person';  | 
7.7 模块的继承
模块也是可以继承的。
1  | export * from 'circle';  | 
8. Map和Set
8.1 Map
8.1.1 基本用法
ES6提供了一个新的数据结构Set,它类似于数组,但是成员的值都是唯一的,没有重复值。
1  | const set = new Set();  | 
1  | const set = new Set([1, 2, 3, 4, 5]);  | 
向Set加入值的时候,不会发生类型装换,所以5和’5’是两个不同的值。Set内部判断两个值是否相等,使用的算法类似于’===’运算符,主要区别是向Set加入值时认为NaN等于自身,而精确相当运算认为NaN不等于NaN,另外,两个对象总是不等的。
8.1.2 Set实例的属性和方法
Set.prototype.constructoy:Set的构造函数;Set.prototype.size:返回Set实例的成员总数;Set.prototype.add(value):添加某个值,返回Set解构本身;Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功;Set.prototype.has(value):返回一个布尔值,表示该值是否是Set的成员;Set.prototype.clear():删除所有成员,没有返回值;Set.prototype.keys():返回键名的遍历器;Set.prototype.values():返回兼职的遍历器;Set.prototype.entries():返回键值对的遍历器;Set.prototype.forEach():使用回调函数遍历每个成员;
由于Set解构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致。
8.2 WeakSet
8.2.1 含义
WeakSet解构与Set类似,也是不重复的值的集合,但是,它与Set有两个区别:
WeakSet的成员只能是对象,而不能是其他类型的值;WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用。如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。
鉴于WeakSet的特殊机制,所以其内部只适合临时存放一组对象,或者跟对象绑定的信息(比如DOM节点信息) ,只要这些对象在外部小时,它在WeakSet里面的引用就会自动消失。所以WeakSet的成员是不适合应用的,因为它随时会消失。另外,由于WeakSet内部有多少个成员,取决于内部垃圾回收机制什么时候运行,运行前后的成员数可能是不同的,而垃圾回收机制何时运行是不可预测的,所以ES6规定WeakSet不可遍历。
8.2.2 方法
WeakSet.prototype.add(value):向WeakSet实例添加一个成员;WeakSet.prototype.delete(value):删除一个成员;WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在WeakSet实例之中。
8.3 Map
8.3.1 基础含义
JavaScript的对象,本质上是键值对的集合,但是传统上只能用字符串作为key值。而ES6解除了这种限制,它的键值可以为任何类型的值,实现了真正意义上的值-值的集合。
1  | const map = new Map();  | 
Map构造函数接受数组以及任何具有Iterator接口的数据结构作为参数,生成一个新的Map实例。
- 数组作为参数
 
1  | const items = [  | 
set实例作为参数
1  | const set = new Set([  | 
map实例作为参数
1  | const params = new Map();  | 
由上可知,Map的键实际上跟内存地址绑定的,只要内存地址不一样,就视为两个键。这就解决了同名属性碰撞的问题。Map同样将NaN视为同一个键。
8.3.2 方法
Map.prototype.size:返回Map解构的成员总数;Map.prototype.set(key, value):设置一组键值对;Map.prototype.get(key):返回key对应的value值;Map.prototype.has(key):判断是否在当前map对象中;Map.prototype.delete(key):删除某个key值;Map.prototype.clear():清除整个map;Map.prototype.keys():返回键名的遍历器;Map.prototype.values():返回键值的遍历器;Map.prototype.entries():返回所有成员的遍历器;Map.prototype.forEach():遍历Map的所有成员;
Map转为数组最方便的方法,就是使用扩展运算符(…)
8.3.3 数据结构互相转换
Map转为数组1
2
3
4
5
6const map = new Map([
['name', 'andy'],
['age', 12]
]);
console.log([...map]);数组转为
Map1
2
3
4const map = new Map([
['name', 'andy'],
['age', 12]
]);Map转对象如果所有
Map的键都是字符串,它可以无损地转为对象。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function mapToObj(map) {
var result = {};
for (let [key, value] of map) {
result[key] = value;
}
return result;
}
var map = new Map([
['name', 'andy'],
['age', 12]
])
var bb = mapToObj(map);对象转
Map对象转
Map可以通过Object.entries,当然也可以自己实现一个转换函数。1
2
3
4
5
6
7
8
9
10
11
12
13
14// 方法一
const person = { name: 'andy', age: 1 };
const map = new Map(Object.entries(person));
// 方法二
function objToMap(obj) {
var map = new Map();
for (let key of Object.keys(obj)) {
map.set(key, obj[key]);
}
return map;
}Map转JSONMap转为JSON要区分两种情况,一种情况是,Map的键名都是字符串,这时可以选择转为对象JSON,另一种情况是,Map的键名有非字符串,这时可以选择转为数组JSON。1
2
3function mapToJSON(map) {
return JSON.stringify(mapToObj(map));
}1
2
3function mapToJSON(map) {
return JSON.stringify([...map]);
}JSON转Map正常情况下,
JSON都可以转为Map。1
2
3function JSONToMap(json) {
return objToMap(JSON.parse(json));
}
8.4 WeakMap
8.4.1 含义
WeakMap与Map解构类似,也是用于生成键值对的集合。他们也有两个区别点
WeakMap只接受对象作为键名(null除外),其他类型不接受。WeakMap的键名所指向的对象,不计入垃圾回收机制。
WeakMap的设计目的在于,有时我们想在对啊ing上面存放一些数据,但是这会行程对于这个对啊ing的引用。一旦不再需要这个对象,我们要必须删除这个应用,否则垃圾回收机制就不会释放其占用的内存。
它最适用的情况还是DOM的处理
1  | const wm = new WeakMap();  | 
8.4.2 语法
WeakMap与Map在API上的区别主要有两个;
- 没有遍历操作,即没有
keys,values,entries方法,也没有size属性,这也是因为不知道何时才会运行垃圾回收机制,其内部的数量有可能会变化。 - 无法清空,即不支持
clear方法 
8.4.3 API
WeakMap只有四个方法可以用:
get()set()has()delete()
9. Proxy
9.1 基础
Proxy用于修改某些默认的操作行为。可以理解为,在目标对象之前架设一层“拦截”,外接对该对象的访问,都必须经过这层拦截。Proxy这个词原意是代理,用在这里表示由他来“代理”某些操作,可以译为“代理器”。
1  | const obj = new Proxy({}, {  | 
Proxy对象的所有用法,都是上面这种形式,不同的只是handler参数的写法,其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数用来定制拦截行为。
9.2 拦截操作
Proxy支持的拦截操作一共有13中:
get(target, propKey, receiver):拦截对象属性的读取;set(target, propKey, value, receiver):拦截对象属性的设置;has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值;delete(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值;ownKeys(target):拦截Object.getOwnPropertyNames(proxy),Object.getOwnPropertySymbols(proxy),Object.keys(proxy),for...in循环,返回一个包含目标对象所有自身属性的数组;getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象;defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc),Object.defineProperties(proxy, propDescs),返回一个布尔值;preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值;getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象;setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值;isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值;apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)。
9.3 优势
在Proxy出世之前,我们用Object.defineProperty来实现一个对象操作的拦截。Proxy相对于Object.defineProperty可谓是一个升级版,那么它究竟有什么优势呢:
支持数组,
Proxy本身支持对数组的拦截,不需要再对数组进行重载,省去了众多hack;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17let arr = [1,2,3]
let proxy = new Proxy(arr, {
get (target, key, receiver) {
console.log('get', key)
return Reflect.get(target, key, receiver)
},
set (target, key, value, receiver) {
console.log('set', key, value)
return Reflect.set(target, key, value, receiver)
}
})
proxy.push(4)
// 能够打印出很多内容
// get push (寻找 proxy.push 方法)
// get length (获取当前的 length)
// set 3 4 (设置 proxy[3] = 4)
// set length 4 (设置 proxy.length = 4)针对对象,
Proxy针对的是整个对象,而非对象中的某个属性,所以也就不需要对keys进行遍历;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17let obj = {
name: 'Eason',
age: 30
}
let handler = {
get (target, key, receiver) {
console.log('get', key)
return Reflect.get(target, key, receiver)
},
set (target, key, value, receiver) {
console.log('set', key, value)
return Reflect.set(target, key, value, receiver)
}
}
let proxy = new Proxy(obj, handler)
proxy.name = 'Zoe' // set name Zoe
proxy.age = 18 // set age 18嵌套支持,本质上,Proxy 也是不支持嵌套的,这点和 Object.defineProperty() 是一样的。因此也需要通过逐层遍历来解决。Proxy 的写法是在 get 里面递归调用 Proxy 并返回,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24let obj = {
info: {
name: 'eason',
blogs: ['webpack', 'babel', 'cache']
}
}
let handler = {
get (target, key, receiver) {
console.log('get', key)
// 递归创建并返回
if (typeof target[key] === 'object' && target[key] !== null) {
return new Proxy(target[key], handler)
}
return Reflect.get(target, key, receiver)
},
set (target, key, value, receiver) {
console.log('set', key, value)
return Reflect.set(target, key, value, receiver)
}
}
let proxy = new Proxy(obj, handler)
// 以下两句都能够进入 set
proxy.info.name = 'Zoe'
proxy.info.blogs.push('proxy')Proxy提供了比Object.defineProperty更多的拦截方法,扩展了很多功能。
10. Reflect
10.1 基础
Reflect对象与Proxy对象一样,也是ES6为了操作对象而提供的新API。
Reflect对象的设计目的有以下几个:
- 将
Object对象的一些明显属于语言内部的方法,放到Reflect上,现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只在Reflect对象上部署。也就是说,从Reflect对象上可以拿到语言内部的方法; - 修改某些
Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false; - 让
Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。 - 与
Proxy对象的方法一一对应。只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。 
10.2 静态方法
Reflect.apply(target, thisArg, args)Reflect.construct(target, args)Reflect.get(target, name, receiver)Reflect.set(target, name, value, receiver)Reflect.defineProperty(target, name, desc)Reflect.deleteProperty(target, name)Reflect.has(target, name)Reflect.ownKeys(target)Reflect.isExtensible(target)Reflect.preventExtensions(target)Reflect.getOwnPropertyDescriptor(target, name)Reflect.getPrototypeOf(target)Reflect.setPrototypeOf(target, prototype)
11. Promise
11.1 基础
所谓Promise,简单说是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。
1  | const promise = new Promise(function(resolve, reject) {  | 
11.2 方法
Promise.all()
Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
1  | const p = Promise.all([p1, p2, p3]);  | 
Promise.all()方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
p的状态由p1、p2、p3决定,分成两种情况。
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
Promise.race()
Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
1  | const p = Promise.race([p1, p2, p3]);  | 
上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
Promise.allSettled()
Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。该方法由 ES2020 引入。
Promise.any()
ES2021 引入了Promise.any()方法。该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。