ES 2015
变化 | 描述 |
---|---|
块级作用域 | 使用let / const 声明变量会生成块级作用域 |
数组解构 | 将数组内元素赋值给相应变量,变量可设置默认值 const [a, b, c] = [100, 200, 300] |
对象解构 | 提取对象属性,可重命名以及设置默认值 const { name : objName = 'jack' } = { name : 'zce', age: 18} |
模板字面串 | 可插入表达式的字符串字面量 const msg = `hei, ${name} --- ${1+2} ----${Math.random()} ` |
参数默认值 | 设置函数参数的默认值 function foo (baz, enable = true, ) {} |
展开语法 | 展开数组 const arr=[1,2,3,4,5]; const arr1=[...arr]; |
剩余参数 | 将一个不定数量的参数表示为一个数组,也可用于数组 function foo (...args) {} |
箭头函数 | () => {} |
对象字面量增强 | 简化对象属性名与变量名相同时的书写 |
Proxy | 用于创建一个对象的代理,从而实现基本操作的拦截和自定义 |
Reflect | 提供拦截 JavaScript 操作的方法, 统一操作对象的方法 |
Promise | 异步编程函数,使用链式调用的方法来解决地狱回调 |
class | 简化原型以及原型链继承 |
Set对象 | 值集合(伪数组)允许你存储任何类型的唯一值,其中的元素只会出现一次。 |
Map对象 | 键值对集合,可以用任意对象类型作为键并记住键的插入顺序 |
Symbol | 基本数据类型,用于创建独一无二的值即唯一值 |
for-of循环 | 可遍历所有可迭代对象,遍历内容为对象值 |
迭代器 | 数据迭代接口,用于for-of循环 |
生成器 | 函数内部语句惰性执行,主要用于迭代器生成以及异步编程 |
ES Modules | js文件模块化 |
ES6 字符串扩展方法 | includes()、startsWidth()、endsWith()、repeat()、String.raw() |
对象扩展方法 | Object.assign()、Object.is()、Object.getOwnPropertySymbols() |
Number基本包装类型扩展 | Number.isNaN()、Number.isFinite()、Number.isInteger()、Number.parseInt()、Number.parseFloat() |
数组扩展方法 | Array.of()、Array.from()、fill()、find()、findIndex()、entries()、keys()、values() |
新增数据类型: Set对象 Map对象 Symbol基本数据类型
基本数据类型: Number、String、Boolean、undefined、null、symbol
引用数据类型: Object
块级作用域
作用域 - 某个成员能够起作用的范围
ES6之前作用域 : 全局作用域 函数作用域
ES6之后作用域: 全局作用域 函数作用域 块级作用域
let & 块级作用域
//无块级作用域 外部也能访问
if (true) {
var foo = 'zce'
}
console.log(foo);
//块级作用域 外部无法访问 会报错
if (true) {
let foo = 'zce'
}
console.log(foo);
//---------------------------------
//var 为全局 仅打印三次
for(var i = 0; i<3; i++){
for(var i = 0; i < 3; i++){
console.log(i);
}
console.log('内层结束 i =' + i);
}
//let 为块级 因此会打印九次
//不建议使用同名的计数器
for(let i = 0; i<3; i++){
//实际解决问题的是内部let
for(let i = 0; i < 3; i++){
console.log(i);
}
console.log('内层结束 i =' + i);
}
//--------------------------------------
//循环注册事件
var elements = [{}, {}, {}]
for (var i = 0; i < elements.length; i++) {
//由于注册事件为下一次宏任务 i为全局变量 因此事件执行打印时i都为3
elements[i].onclick = function () {
//此时i 都是3
console.log(i);
}
}
elements[0].onclick()
//使用闭包来解决问题
var elements = [{}, {}, {}]
for (var i = 0; i < elements.length; i++) {
//立即执行函数 使用函数作用域摆脱全局作用域的影响
elements[i].onclick = (function (i) {
return function () {
console.log(i);
}
})(i)
}
elements[0].onclick()
//块级作用域
var elements = [{}, {}, {}]
for (let i = 0; i < elements.length; i++) {
elements[i].onclick = function () {
console.log(i);
}
}
elements[0].onclick()
//---------------------------------------------------------
// let 会形成一个闭包
for (let i = 0; i < 3; i ++) {
let i = 'foo'
//正常打印 互不影响
console.log(i);
}
let i = 0
if (i < 3) {
let i = 'foo'
console.log((i));
}
i++
//---------------------------------------------------
// let的变量声明不会提升 var的变量声明会提升到最开始
console.log(foo);
var foo = 'zce'
//let必须先声明再使用
console.log('foo');
let foo = 'zce'
const & 块级作用域
const用于声明一个只读的恒量/常量,在let基础上增加一个只读特性 – 变量声明过后就不允许被修改
// const name = 'zce'
//报错 声明后不允许被修改新的内存地址
name = 'jack'
//声明时必须赋初始值
const name
//报错
name = 'zce'
//const不允许被修改内存地址,而不是不允许修改恒量中的属性成员
//以下被允许
const obj = {}
obj.name = 'zce'
//以下不允许 改变内存指向
obj = {}
最佳实践: 不用var 主用const 配合let
数组解构
//数组的解构
const arr = [100, 200, 300]
//以前需要通过索引去访问数组的各个值
const foo = arr[0]
const bar = arr[1]
const baz = arr[2]
//数组的解构 依次对应数组内的成员
const [foo, bar, baz] = arr
console.log(foo, bar, baz);
//若只获取某个成员 确保解构与数组一致
const [, , baz] = arr
console.log(baz);
// 数组剩余参数
// 变量名前加... 表示获取当前位置后的所有成员 返回数组
// 该方法只能在解构位置的最后一个成员上使用
const [foo, ...rest] = arr
console.log(rest);
//解构数量小于数组长度 按顺序提取 多余的则不会被提取
const [foo] = arr
console.log(foo);
//解构数量大于数组长度 多余的则为undefined
const [foo, baz, bac, more] = arr
console.log(more);
//给未提取到的成员设置默认值 已提取到数据的成员默认值无用
const[foo, bar, baz = 123, more = 'default value'] = arr
console.log(baz, more);
//解构可以使一些操作简化
const path = '/foo/bar/baz'
const tmp = path.split('/')
const rootdir = tmp[0]
console.log(rootdir);
// 可简化=>
const [, rootdir] = path.split('/')
console.log(rootdir);
对象解构
对象的解构根据属性名提取,而不是位置
// 对象的解构
const obj = { name : 'zce', age: 18}
//解构中的变量名需要匹配对象中的属性并提取值
const { name } = obj
console.log(name);
//对解构对象属性进行重命名 否则同名会引起冲突
const name = 'tom'
//解构内左边是匹配对象中的属性名 右边是重命名的变量名
//若还需要赋值 则在后面继续添加
const { name : objName = 'jack' } = obj
console.log(objName);
//简化方法 减少代码
const { log } = console
log('foo')
log('bar')
log('123')
模板字符串
// 模板字符串
const str = 'hello es2015, this is a string'
// 模板字符串使用反引号
// 1.模板字符串支持换行 多行字符串
const str = `hello es2015,
this is a \`string\``
console.log(str);
// 2.支持插值表达式的方式 ${} 在字符串中嵌入所需值
const name = 'tom'
const msg = `hei, ${name} --- ${1+2} ----${Math.random()} `
console.log(msg);
优点:
模板字符串支持换行 多行字符串
支持插值表达式的方式 ${} 在字符串中嵌入所需值
带标签的模板字符串
// 带标签的模板字符串
//标签是一个特殊的函数 添加该标签即调用该函数
//此时为打印一个数组 ['hello world']
const str = console.log`hello world`
const name = 'tom'
const gender = false
//返回值是模板字符串的返回值
function myTagFunc (strings, name, gender) {
// console.log(strings, name, gender);
// retuen '123'
const sex = gender? 'man' : 'woman'
return strings[0] + name + strings[1] + sex + strings[2]
}
//按照表达式分割的静态内容 标签方法可接收到插入表达式的值
const result = myTagFunc`hey, ${name} is a ${gender}`
console.log(result);
//标签字符串作用 对模板字符串进行加工
模板字符串 & 原始字符串
在标签函数的第一个参数中,存在一个特殊的属性raw ,我们可以通过它来访问模板字符串的原始字符串,而不经过特殊字符的替换。
function tag(strings) {
console.log(strings.raw[0]);
}
tag`string text line 1 \n string text line 2`;
// logs "string text line 1 \n string text line 2" ,
// including the two characters '\' and 'n'
使用String.raw() 方法创建原始字符串和使用默认模板函数和字符串连接创建是一样的。
var str = String.raw`Hi\n${2+3}!`;
// "Hi\n5!"
str.length;
// 6
str.split('').join(',');
// "H,i,\,n,5,!"
参数默认值
// 函数参数的默认值
// ES6之前
function foo (enable) {
// 设置默认值使用逻辑判断
//当传入参数为false时 则会返回true
// enable = enable || true
enable = enable === undefined ? true : enable
console.log('foo invoked - enable');
console.log(enable);
}
foo()
// E6后
//带有默认值的形参需要放在最后 否则次序传参时覆盖默认值
function foo (baz, enable = true, ) {
console.log('foo invoked - enable');
console.log(enable);
}
foo(false)
展开语法
在函数调用/数组构造时, 将数组表达式或者string在语法层面展开。
// 展开数组参数
// 函数调用 - 数组展开
const arr = ['foo', 'bar', 'baz']
//ES6之前
// console.log(
// arr[0],
// arr[1],
// arr[2],
// );
//使用apply方法
// console.log.apply(console, arr);
//ES6之后
console.log(...arr);
// 数组构造 - 数组展开
var parts = ['shoulders', 'knees'];
var lyrics = ['head', ...parts, 'and', 'toes'];
// ["head", "shoulders", "knees", "and", "toes"]
剩余参数
将一个不定数量的参数表示为一个数组。
// 剩余参数
//ES6之前使用aruments 获取传入参数 得到伪数组
function foo () {
console.log(arguments);
}
//使用参数展开符 只能使用在形参的最后一位 且只能使用依次
function foo (...args) {
console.log(args);
}
foo(1, 2, 3, 4);
剩余参数和 arguments对象的区别:
- 剩余参数只包含那些没有对应形参的实参,而arguments对象包含了传给函数的所有实参。
- arguments对象不是一个真正的数组,剩余参数是真正的 Array实例,也就是说你能够在它上面直接使用所有的数组方法,比如 sort,map,forEach或pop。
- arguments对象还有一些附加的属性(如callee属性)。
箭头函数
箭头函数表达式的语法比函数表达式更简洁,并且没有自己的this,arguments,super或new.target。
适用范围:需要匿名函数的地方,并且它不能用作构造函数。
// 箭头函数
//ES6之前
function inc (number) {
return number + 1
}
console.log(inc(100));
//ES6之前 方便简单 简化回调函数
// 左边为参数 右边则为函数体
// 右边函数体仅有一句话 则作为返回值
// 若有花括号 则手动写return
const inc = (n,m) =>{
console.log('inc invoked');
return n+1
}
console.log(inc(100));
// eg
const arr = [1, 2, 3, 4, 5, 6, 7]
//普通函数写法
arr.filter(function (item) {
return item % 2
})
//箭头函数写法
arr.filter(item => item % 2)
箭头函数特点
- 箭头函数没有单独的this,即不会创建自己的this,它只会从自己的作用域链的上一层继承this。因此使用apply、call、bind调用箭头函数时第一个参数会被忽略
// 箭头函数与this
const person = {
name : 'tom',
// 函数声明 this 指向使用该函数的对象
// sayHi: function () {
// console.log(`my name is ${this.name}`);
// }
// 箭头函数当中没有this的机制 指向定义外部 即与外部this一致
sayHi: () => {
console.log(`my name is ${this.name}`);
},
sayHiAsync: function () {
// const _this = this
// //此时setTimeout内部的函数会在全局对象中调用 因此this指向window
// setTimeout(function () {
// console.log(_this.name);
// }, 1000)
//使用箭头函数 this会获取外层的this
setTimeout(() => {
console.log(this.name);
}, 1000)
}
}
person.sayHiAsync()
- 箭头函数不绑定Arguments 对象。
function foo(n) {
// 隐式绑定 foo 函数的 arguments 对象. arguments[0] 是 n,即传给foo函数的第一个参数
var f = () => arguments[0] + n;
return f();
}
foo(1); // 2
// 在大多数情况下,使用剩余参数是相较使用arguments对象的更好选择。
function foo(arg1,arg2) {
var f = (...args) => args[1];
return f(arg1,arg2);
}
foo(1,2); //2
对象字面量增强
// 对象字面量
const bar = '345'
const obj = {
foo: 123,
//ES6之前
// bar: bar
// method1: function() {
// console.log('methodqqqq');
// },
//ES6之后
bar,
method1 () {
console.log('method111');
//指向obj
console.log(this);
},
//报错
// Math.random() : 123
//ES6之后 计算属性名 内部可以使用任意表达式
[Math.random()]: 123
}
//ES6之前按动态进行属性赋值
obj[Math.random()] = 123
console.log(obj);
obj.method1()
Proxy
可使用Object.definedProperty为对象添加属性,捕获对象的读写过程
Proxy在ES6当中专门设置对象来访问代理器 监视对象的读写过程 使用proxy实现数据内部响应
// Proxy 对象
const person = {
name : 'zce',
age: 20
}
//第一个参数为代理目标对象 第二个参数为代理处理对象
const personProxy = new Proxy(person, {
// 监视处理对象的访问
// 第一个参数为监听对象 第二个为访问的属性
get (target, property) {
return property in target ? target[property] : 'default'
// console.log(target, property);
// return 100
},
// 监视设置属性的过程
// 第一个参数为监听对象 第二个为写入名称 第三个为写入值
set (target, property, value) {
if (property === 'age') {
if(!Number.isInteger(value)) {
throw new TypeError(`${value} is not an int`)
}
}
target[property] = value
// console.log(target, property, value);
}
})
personProxy.age = 100
personProxy.gender = true
console.log(personProxy.name);
console.log(personProxy.xxx);
Proxy VS Object.defineProperty()
Proxy
- 更强大 能够监视到更多对象操作(delete \ 方法调用)
- 更好的支持数组对象的监视 - 重写数组的操作方法(push\pop等)
- 以非侵入的方式监管了对象的读写
Object.defineProperty()只能监视属性的读写
//Proxy 对比 Object.definedProperty()
const person = {
name : 'zce',
age: 20
}
const personProxy = new Proxy (person, {
deleteProperty (target, property) {
console.log('delete', property);
delete target[property]
}
})
delete personProxy.age
console.log(person);
//使用Proxy监视数组对象
const list = []
const listProxy = new Proxy(list, {
set (target, property, value) {
console.log('set', property, value);
target[property] = value;
return true //表示设置成功
}
})
listProxy.push(100)
Reflect
Reflect用于统一的对象操作API,属于一个静态类因此不能使用new操作符创建 使用时如Math对象 Reflect.get()
Reflect内部封装了一系列对对象的底层操作,提供了十四个静态方法(其中一个方法已经弃用),Reflect成员方法就是Proxy处理对象的默认实现
Reflect十四种方法文档查看
最大的用处: 统一提供一套用于操作对象的API
// Reflect 对象
// const obj = {
// foo: '123',
// bar: '456'
// }
// // 监听对象操作 使用Reflect
// const proxy = new Proxy(obj, {
// get (target, property) {
// console.log('watch logic~');
// return Reflect.get(target, property)
// }
// })
// console.log(proxy.foo);
const obj = {
name: 'zce',
age: 18
}
// ES6之前
// console.log('name' in obj);
// console.log(delete obj['age']);
// // 获取对象中的所有属性名
// console.log(Object.keys(obj));
// 统一对象的处理方法 避免 操作符 方法 等混杂使用
console.log(Reflect.has(obj, 'name'));
console.log(Reflect.deleteProperty(obj, 'age'));
console.log(Reflect.ownKeys(obj));
Promise
Promise是一种更优的异步编程解决方案,使用链式调用解决了传统异步编程中回调函数嵌套过深的问题
// promise的使用
const myFirstPromise = new Promise((resolve, reject) => {
// ?做一些异步操作,最终会调用下面两者之一:
//
// resolve(someValue); // fulfilled
// ?或
// reject("failure reason"); // rejected
});
更详细的promise解析见promise笔记
class
类的声明
// class 关键词
// function Person (name) {
// this.name = name
// }
// //使用原型在实例间共享方法
// Person.prototype.say = function () {
// console.log(`hi, my name is ${this.name}`);
// }
// ES6后
class Person {
constructor (name) {
this.name = name
}
say () {
console.log(`hi, my name is ${this.name}`);
}
}
const p = new Person('zce')
p.say()
静态方法
实例当中的方法分为实例方法和静态方法
实例方法通过实例对象调用
静态方法通过类型本身调用
静态方法是通过构造函数上挂载方法实现
ES5中新增添加静态成员的static关键词
// static方法
class Person {
constructor (name) {
this.name = name
}
say () {
console.log(`hi, my name is ${this.name}`);
}
static create (name) {
//静态方法的this不会指向实例 而是类型
return new Person(name)
}
}
const tom = Person.create('tom')
tom.say()
类的继承
extends 专门用于类的继承
// extends 继承
class Person {
constructor (name) {
this.name = name
}
say () {
console.log(`hi, my name is ${this.name}`);
}
}
class Student extends Person {
constructor (name, number) {
//super始终指向父类 调用其则是调用父类的构造函数
super(name)
this.number = number
}
hello () {
super.say()
console.log(`my school number is ${this.number}`);
}
}
const s = new Student('jack', '100')
s.hello()
Set对象
set是ES6当中的全新数据结构 可以理解为集合, 与传统中的数组非常类似,set中的数据成员不允许重复
- 该特性可用于数组去重,该set对象是个伪数组
// Set 数据结构
// 用于存放不重复的数据
const s = new Set()
//add方法会返回集合本身 若添加已存在的值 则会被忽略
s.add(1).add(2).add(3).add(4).add(2)
console.log(s);
//遍历数据 则使用forEach方法
s.forEach(i => console.log(i))
// 使用ES6中的of循环
for(let i of s) {
console.log(i);
}
// 可通过size属性获取集合的长度
console.log(s.size);
// has => 判定集合中是否存在某个值
console.log(s.has(100));
// delete => 删除集合当中的指定值 删除成功则返回true
console.log(s.detele(3));
console.log(s);
//clear => 清楚集合中的全部内容
s.clear()
console.log(s);
//常见的场景 用于为数组中的数据去重
const arr = [1, 2, 1, 3, 4, 1]
// Array.from ES6新增的方法 将伪数组转换为数组
const result = Array.from(new Set(arr))
const result = [...new Set(arr)]
console.log(result);
Map对象
map与object类似,本质上都是键值类集合,但对象当中的键只能是字符串类型,而map则可以用任意对象类型作为键
// Map 数据结构
// const obj = {}
// obj[true] = 'value'
// obj[123] = 'value'
// obj[ {a: 1}] = 'value'
// console.log(Object.keys(obj));
// //无法区分值
// console.log(obj[{}]);
// console.log(obj['[object Object]']);
// map真正意义上的键值对集合 用于映射两者间的关系
const m = new Map()
const tom = { name: 'tom'}
m.set(tom, 90)
console.log(m);
console.log(m.get(tom));
// m.has()
// m.delete()
// m.clear()
// 第一个为遍历的值 第二个为遍历的键
m.forEach((value, key) => {
console.log(value, key);
})
Symbol
Symbol最主要的作用就是为对象添加独一无二的属性名
7种数据类型: number string boolean null undefined object symbol
未来的新增数据 bigInt 用于存放更长的数据
// Symbol 数据类型 表示一个独一无二的值
// shared.js =========================================
const cache = {}
// a.js ========================================
cache['a_foo'] = Math.random()
// b.js =================================
cache['b_foo'] = '123'
// 产生冲突
console.log(cache);
const s =Symbol()
console.log(s);
console.log(typeof s); //Symbol
console.log(
// 创建的值唯一 不能重复
Symbol() === Symbol() //false
);
// 传入字符串作为描述文本 对象可以使用Symbol作为属性名
console.log(Symbol('foo'));
console.log(Symbol('bar'));
console.log(Symbol('baz'));
// 对象的键值可以使用字符串 和 Symbol 避免对象属性名重复问题
const obj = {}
obj[Symbol()] = '123'
obj[Symbol()] = '456'
console.log(obj);
const obj = {
[Symbol()]: 123
}
console.log(obj);
// a.js ====================================
// Symbol还可以模拟实现对象的私有成员
const name = Symbol()
const person = {
[name]: 'zce',
say () {
console.log(this[name]);
}
}
// b.js ================================
// person[Symbol()] //无法访问同样的属性
person.say()
for-of循环
for - 适用于遍历普通的数组
for-in - 适用于遍历键值对
一些对象的遍历方法
这些遍历方式都有一定的局限性
ES6引入全新的for - of循环, 作为遍历所有数据结构的统一方式 可遍历任何一种可迭代的数据结构
// for-of 循环 可遍历 数组 伪数组 等
//遍历数组
const arr =[100, 200, 300, 400]
for (const item of arr) {
console.log(item);
}
// 用于取代forEach =>
// forEach不能跳出循环
arr.forEach( item => {
console.log(item);
})
//可使用break关键词随时终止循环
for (const item of arr) {
console.log(item);
if (item > 100){
break
}
}
// 以前为了能够随时终止遍历 使用some或者every
arr.some() // 返回 true 终止遍历
arr.every() // 返回 false 终止遍历
// 遍历Set对象
const s = new Set(['foo', 'bar'])
for (const item of s) {
console.log(item);
}
// 遍历map对象
const m = new Map()
m.set('foo', '123')
m.set('bar', '345')
for (const [key, value] of m) {
console.log(key, value);
}
// 遍历普通对象 //会报错
// ES6中需要给其他遍历的数据类型使用 Iterable接口 - 可迭代接口
// 使用Iterable 接口是for - of 前提
const obj = { foo: 123, bar: 456}
for (const item of obj) {
console.log(item);
}
迭代器
ES6中需要给其他遍历的数据类型使用 Iterable接口 - 即迭代器, 使用Iterable 接口是for - of 前提
迭代器是一个对象,它定义一个序列,并在终止时可能返回一个返回值。
迭代器是通过使用 next() 方法实现 Iterator protocol 的任何一个对象,该方法返回具有两个属性的对象: value,这是序列中的 next 值;和 done ,如果已经迭代到序列中的最后一个值,则它为 true 。如果 value 和 done 一起存在,则它是迭代器的返回值。
// 迭代器 Interator
// 所有可循环 / 迭代的数据类型都必须要实现Interator这个接口(迭代器)
// 即类型内部挂载一个Interator的方法 而Interator方法必须要有一个next()方法对象
// 不断调用next()方法即可完成对数据的遍历
const set = new Set(['foo', 'bar', 'baz'])
const iterator = set[Symbol.iterator]()
//正常遍历
console.log(iterator.next()); // { value: 'foo', done: false }
console.log(iterator.next()); // { value: 'bar', done: false }
console.log(iterator.next()); // { value: 'baz', done: false }
console.log(iterator.next()); // { value: undefined, done: true }
console.log(iterator.next()); // { value: undefined, done: true }
实现迭代器
// 实现可迭代接口 (Iterable)
// 实现可迭代接口
const obj = {
// 必须含有迭代器的iterator方法
[Symbol.iterator]: function () {
// 实现了迭代器接口 iterator 内部必须含有一个用于迭代的next方法
return {
next: function () {
// 实现了迭代结果接口 必须有value 用于表示当前迭代值 和 done 用于表示迭代是否结束
return {
value: 'zce',
done: true
}
}
}
}
}
// eg
const obj = {
store: ['foo', 'bar', 'baz'],
[Symbol.iterator]: function () {
let index = 0
const self = this
return {
next: function () {
const result = {
value: self.store[index],
done: index >= self.store.length
}
index++
return result
}
}
}
}
for (const item of obj) {
console.log('循环体', item);
}
// 因为生成器自带next方法,因此可使用生成器简化写法
const obj = {
store: ['foo', 'bar', 'baz'],
*[Symbol.iterator]() {
const data = this.store
for(let i=0, max=data.length; i<max; i++){
yield data[i]
}
}
}
for (const item of obj) {
console.log('循环体', item);
}
// 使用yield* 迭代操作数,简化对象迭代
const obj = {
store: ['foo', 'bar', 'baz'],
*[Symbol.iterator] () {
yield* this.store;
}
}
for (const item of obj) {
console.log('循环体', item);
迭代器设计模式
// 迭代器设计模式
// 场景: 你我协同开发一个任务清单应用
// 我的代码 ================================
const todos = {
life: ['吃饭', '睡觉', '打豆豆'],
learn: ['语言', '数学', '外语'],
work: ['喝茶'],
// 对外提供一个的接口 调用者不必关心调用的结构 不用担心改变所带来的影响
each: function (callback) {
const all = [].concat(this.life, this.learn, this.work)
for (const item of all ) {
callback(item)
}
},
//使用可迭代接口方式解决该问题
[Symbol.iterator]: function() {
const all = [...this.life, ...this.learn, ...this.work]
let index = 0
return {
next: function () {
return {
value: all[index],
done: index++ >= all.length
}
}
}
}
}
// 你的代码 ==================={==============
// for (const item of todos.life) {
// console.log(item);
// }
// for(const item of todos.learn) {
// console.log(item);
// }
todos.each(function (item) {
console.log(item);
})
console.log('-----------------------------------------------');
for (const item of todos) {
console.log(item);
}
// 对外提供统一的接口 使得外部不用关心内部结构如何
// 其适用于任何数据类型
生成器
generator主要用于构造迭代器,也可用于避免异步编程中回调嵌套过深,提供更好的异步编程解决方案
// Generator 函数
// function * foo () {
// console.log('zce');
// return 100
// }
// // 返回生成器对象
// const result = foo()
// // 使用next 执行函数体
// console.log(result.next());
function * foo () {
console.log('111');
yield 100
console.log('222');
yield 200
console.log('333');
yield 200
}
const generator = foo()
// yield 100后未执行
console.log(generator.next());
// yield 200后未执行
console.log(generator.next());
// yield 300后未执行
console.log(generator.next());
// 用于构造迭代器
var myIterable = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
}
for (let value of myIterable) {
console.log(value);
}
// 使用 yield* 构造迭代器
// yield* 表达式转换操作数,并产生它返回的每个值。
// yield*表达式本身的值是当迭代器关闭时返回的值(即done为true时)。
function* g1() {
yield 2;
yield 3;
yield 4;
}
function* g2() {
yield* g1()
}
var iterator = g2();
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: 4, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
生成器函数会自动返回一个生成器对象,调用该对象的next方法才会开始执行函数体,执行过程中遇到yield关键词 则会暂停执行,且yield后的值会作为结果返回。在继续调用next 则从yield暂停之处继续往下执行 ,直到函数体执行完毕。 最大的特点: 惰性执行
生成器应用
// Generator 应用
// 案例1:发号器
function * createIdMaker () {
let id = 1
while (true) {
yield id++
}
}
const idMaker = createIdMaker()
console.log(idMaker.next().value);
console.log(idMaker.next().value);
console.log(idMaker.next().value);
console.log(idMaker.next().value);
console.log(idMaker.next().value);
// 案例2:使用 Generator 函数实现iterator 方法
const todos = {
life: ['吃饭', '睡觉', '打豆豆'],
learn: ['语言', '数学', '外语'],
work: ['喝茶'],
[Symbol.iterator]: function * () {
const all = [...this.life, ...this.learn, ...this.work]
for (const item of all)
yield item
}
}
for (const item of todos) {
console.log(item);
}
ES Modules
后续会单独写ES Modules相关笔记
字符串扩展方法
常用的字符串扩展方法:
includes()、startsWidth()、endsWith()
判断字符串当中是否包含指定内容
// 字符串的扩展方法
const message = 'Error: foo is not defined.'
console.log(
//判断字符串开始内容
// message.startsWith('Error')
//判断字符串结尾内容
// message.endsWith('.')
//判断字符串中间的内容
message.includes('is')
);
其余可用的扩展方法: repeat()、String.raw()
// repeat()返回字符串重复n次的副本
'abc'.repeat(-1) // RangeError
'abc'.repeat(0) // ''
'abc'.repeat(1) // 'abc'
'abc'.repeat(2) // 'abcabc'
'abc'.repeat(3.5) // 'abcabcabc' (count will be converted to integer)
'abc'.repeat(1/0) // RangeError
// 用于获取模板字符串的原始字符串形式
// 处理替换(例如${foo}),而转义(例如\n)不被处理。
const filePath = String.raw`C:\Development\profile\aboutme.html`;
console.log(`The file was uploaded from: ${filePath}`);
// expected output: "The file was uploaded from: C:\Development\profile\aboutme.html"
对象扩展方法
Object.assign() : 将多个源对象中的属性复制到一个目标对象中 源对象的属性会覆盖掉目标对象当中的属性
// Object.assign 方法
const source1 = {
a: 123,
b: 1234,
}
const source2 = {
b: 789,
d: 789
}
const target = {
a: 456,
c: 456
}
// 第一个参数是目标对象 第二个参数之后是源对象
// 将第二个及之后对象的参数复制到第一个对象 返回第一个对象
const result = Object.assign(target, source1, source2)
console.log(target);
console.log(result === target);
function func (obj) {
//会改变外部obj
// obj.name = 'func obj'
// console.log(obj);
const funcObj = Object.assign({}, obj)
funcObj.name = 'func obj'
console.log(funcObj);
}
const obj = {name: 'global obj'}
func(obj)
console.log(obj);
Object.is() : 用于判断两个值是否相等
// Object.is
console.log(
// 0 == false //自动转换类型 true
// 0 === false //不自动转换类型 false
// +0 === -0 //true
// NaN === NaN //false
// Object.is(+0, -0) //false
// Object.is(NaN, NaN) //true
);
Object.getOwnPropertySymbols(): 返回直接在给定对象上找到的所有符号属性(Symbol)的数组。
const object1 = {};
const a = Symbol('a');
const b = Symbol.for('b');
object1[a] = 'localSymbol';
object1[b] = 'globalSymbol';
const objectSymbols = Object.getOwnPropertySymbols(object1);
console.log(objectSymbols[0]);
// expected output: 2
Number基本包装类型扩展
Number.isNaN()、Number.isFinite()、Number.isInteger()、Number.parseInt()、Number.parseFloat()
// Number.isNaN(): 如果值为NaN并且类型为Number,则返回true;否则为false。
Number.isNaN(NaN); // true
Number.isNaN(Number.NaN); // true
Number.isNaN(0 / 0); // true
Number.isNaN('NaN'); // false
Number.isNaN(true); // false
Number.isNaN(null); // false
Number.isNaN(37); // false
// Number.isFinite(): 确定传递的值是否为有限数字
Number.isFinite(NaN); // false
Number.isFinite(-Infinity); // false
Number.isFinite(0/0); // false
Number.isFinite(10/5); // true
Number.isFinite(0); // true
Number.isFinite(2e64); // true
// Number.isInteger(): 确定传递的值是否为整数
Number.isInteger(-100000); // true
Number.isInteger(99999999999999999999999); // true
Number.isInteger(0.1); // false
Number.isInteger(Math.PI); // false
// Number.parseInt()、Number.parseFloat(): 解析字符串转为整数/浮点数
Number.parseInt('10'); // 10
Number.parseInt('0xf', 16); // 15
Number.parseFloat('0.25'); // 0.25
数组扩展方法
Array.of()、Array.from()、fill()、find()、findIndex()、entries()、keys()、values()
// Array.of(): 从可变数量的参数创建新实例,而不考虑参数的数量或类型
// Array.of(7)创建一个具有单个元素的数组7,而Array(7)创建一个具有length 属性的空数组7
Array.of(7); // [7]
Array(7); // array of 7 empty slots
Array.of(1, 2, 3); // [1, 2, 3]
Array(1, 2, 3); // [1, 2, 3]
// Array.from(): 从阵列状或迭代的对象实例创建一个新的,浅复制Array。
// 伪数组转换成数组的方法
console.log(Array.from('foo'));
// expected output: Array ["f", "o", "o"]
console.log(Array.from([1, 2, 3], x => x + x));
// expected output: Array [2, 4, 6]
// arr.fill(value, start, end): 将数组中的所有元素都更改为静态值,从开始索引(默认0)到结束索引(默认array.length)。它返回修改后的数组。
const array1 = [1, 2, 3, 4];
// fill with 0 from position 2 until position 4
console.log(array1.fill(0, 2, 4));
// expected output: [1, 2, 0, 0]
// fill with 5 from position 1
console.log(array1.fill(5, 1));
// expected output: [1, 5, 5, 5]
console.log(array1.fill(6));
// expected output: [6, 6, 6, 6]
// arr.find(fn): 返回满足条件函数的值
const array1 = [5, 12, 8, 130, 44];
const found = array1.find(element => element > 10);
console.log(found);
// expected output: 12
// arr.findIndex(fn): 返回满足条件函数值的数组下标
const array1 = [5, 12, 8, 130, 44];
const isLargeNumber = (element) => element > 13;
console.log(array1.findIndex(isLargeNumber));
// expected output: 3
// arr.entries(): 返回一个新的Array Iterator对象,该对象包含数组中每个索引的键/值对
const a = ['a', 'b', 'c'];
for (const [index, element] of a.entries())
console.log(index, element);
// 0 'a'
// 1 'b'
// 2 'c'
// keys()、values(): 前者获取数组的键,后者获取数组的值
var arr = ['a', , 'c'];
var sparseKeys = Object.keys(arr);
var denseKeys = [...arr.keys()];
console.log(sparseKeys); // ['0', '2']
console.log(denseKeys); // [0, 1, 2]
var arr = ['a', 'b', 'c', 'd', 'e'];
var iterator = arr.values();
for (let letter of iterator) {
console.log(letter);
} //"a" "b" "c" "d" "e"
ES 2016
变化 | 描述 |
---|---|
数组扩展方法 | includes() |
运算符扩展 | 新增指数运算符(**) |
ES2016-数组扩展方法
// Array.prototype.includes ------------------------------------
// ES2015中的includes 是字符串的 ES2016中的includes是数组的
const arr = ['foo', 1, NaN, false]
console.log(arr.indexOf('foo')); //0
console.log(arr.indexOf('bar')); //-1
console.log(arr.indexOf(NaN)); //-1
// 补充的数组includes
console.log(arr.includes('foo')); //true
console.log(arr.includes(NaN)); //true
运算符扩展
// 指数运算符 --------------------------------------------
console.log(Math.pow(2, 10));
// 补充的指数运算符 **
console.log(2 ** 10);
ES 2017
变化 | 描述 |
---|---|
对象扩展方法 | Object.values()、Object.entries()、ObjectgetOwnPropertyDescriotors() |
字符串扩展方法 | padStart()、padEnd() |
参数添加尾逗号 | function foo (bar, baz, ) {} |
Async/await语法糖 | 异步编程语法糖,简化异步编程 |
es2017-对象扩展方法
Object.values()、Object.entries()、ObjectgetOwnPropertyDescriotors()
const obj = {
foo: 'value1',
bar: 'value2'
}
// Ovject.values --------------------------------------------
// 返回obj中所有的值组成的数组
console.log(Object.values(obj));
// Object.entries ==================================================
// 以数组的方式返回对象中的所有键值对
console.log(Object.entries(obj));
for (const [key, value] of Object.entries(obj)) {
console.log(key, value);
}
console.log(new Map(Object.entries(obj)));
// Object.getOwnPropertyDescriotors ----------------------------------
// 获取对象当中属性的完整描述信息
const p1 = {
firstName: 'Lei',
lastName: 'Wang',
get fullName () {
return this.firstName + ' ' + this.lastName
}
}
console.log(p1.fullName);
const p2 = Object.assign({}, p1)
p2.firstName = 'zce'
console.log(p2); // fullName: 'Lei Wang'
// 处理 get set 等
const descriptors = Object.getOwnPropertyDescriptors(p1)
// console.log(descriptors);
const p2 = Object.defineProperties({}, descriptors)
p2.firstName = 'zce'
console.log(p2.fullName); // zce Wang
es2017-字符串扩展方法
padStart()、padEnd()
// String.property.padStart / String.property.padEnd ------------------------------
// 字符串填充方法
const books = {
html: 5,
css: 16,
javascript: 128
}
for (const [name, count] of Object.entries(books)) {
console.log(name, count);
}
// 用给定的字符串填充开始或末尾的位置 直到达到给定的字符数为止
for (const [name, count] of Object.entries(books)) {
console.log(`${name.padEnd(16, '-')}|${count.toString().padStart(3, '0')}`);
}
参数添加尾逗号
// function foo (
// bar,
// baz,
// ) {
// }
// 结束末尾添加尾逗号 可以让源代码管理工具更精确判断代码改变位置
const arr = [
100,
200,
300,
]
Async/await语法糖
标准化了 Async函数 配合Await关键词 彻底解决了回调函数嵌套过深产生的问题 使得代码简洁易读
function resolveAfter2Seconds(x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x);
}, 2000);
});
};
const add = async function(x) { // async function expression assigned to a variable
let a = await resolveAfter2Seconds(20);
let b = await resolveAfter2Seconds(30);
return x + a + b;
};
add(10).then(v => {
console.log(v); // prints 60 after 4 seconds.
});
(async function(x) { // async function expression used as an IIFE
let p_a = resolveAfter2Seconds(20);
let p_b = resolveAfter2Seconds(30);
return x + await p_a + await p_b;
})(10).then(v => {
console.log(v); // prints 60 after 2 seconds.
});
ES 2018
变化 | 描述 |
---|---|
展开语法扩展 | 在对象中使用展开语法 |
剩余参数扩展 | 在对象中使用剩余参数 |
增强正则表达式 | 环视(向后否定、向后肯定)、正则组命名 |
for await … of循环 | 循环遍历异步可迭代对象以及同步可迭代对象 |
Promise扩展方法 | finally() |
异步迭代器 | Symbol.asyncIterator,用于for await …of 异步循环 |
异步生成器 | 用于构造异步迭代器与异步编程 |
展开语法扩展
const obj = {a: 1, b: 2, c: 3};
const obj1 = {obj: {...obj}};
console.log(obj1);
// { obj: { a: 1, b: 2, c: 3 } }
剩余参数扩展
const obj = { one: 1, two: 2, three: 3, four: 4}
const {one, four, ...rest } = obj
// one => 1 four => 4
// rest => {two: 2, three: 3}
增强正则表达式
// 环视 匹配时左顾右盼
const intro = '张三是张三,张三丰是张三丰,张三不是张三丰,张三丰也不是张三'
// 向后否定 || 逆向肯定 ? 判断标识
const res = intro.replace(/张三(?!丰)/g, '李四')
// 向后肯定 || 正向肯定
const res = intro.replace(/张三(?=丰)/g,'李四')
console.log(res)
// 正则组命名
const date = '2020-05-20'
const reg = /(?<year>\d{4})-(?<mouth>\d{2})-(?<day>\d{2})/
const res = reg.exec(date)
console.log(res)
for await … of
for await…of语句创建一个循环,该循环遍历异步可迭代对象以及同步可迭代对象,包括: 内置的String,Array,类似数组对象,和用户定义的异步/迭代同步器它使用对象的每个不同属性的值调用要执行的语句来调用自定义顺序钩子。
async function* asyncGenerator() {
var i = 0;
while (i < 3) {
yield i++;
}
}
(async function() {
for await (num of asyncGenerator()) {
console.log(num);
}
})();
// 0
// 1
// 2
// 迭代器
function createIdMaker (max) {
let index = 1
return {
next () {
if (index > max) return {done: true}
return {value: index++, done:false}
}
}
}
const idMaker = createIdMaker(2)
console.log(idMaker.next())
console.log(idMaker.next())
console.log(idMaker.next())
// 02.可迭代对象 (Iterable)=============================
const idMaker = {
max: 10,
[Symbol.iterator] () {
let index = 1
const self = this
return {
next () {
if (index > self.max) return { done: true }
return { value: index++, done: false }
}
}
}
}
for await (const item of idMaker) {
console.log(item)
}
Promise扩展方法
new Promise((resolve, reject) => {
setTimeout(() => {
const now = Date.now()
now * 2 ? resolve(now) : reject(now)
}, 1000)
})
.then(now => {
console.log('resolve', now)
})
.catch(now => {
console.log('rejected', now)
})
// 不管成功失败都会执行,用于释放资源
.finally(now => {
console.log('finally', now)
})
异步迭代器
通过设置[Symbol.asyncIterator]属性来自定义异步可迭代对象,可用于for await…of循环。
const idMaker = {
max: 10,
[Symbol.asyncIterator] () {
let index = 1
const self = this
return {
next () {
if (index > self.max) return Promise.resolve({ done: true })
return Promise.resolve({ value: index++, done: false })
}
}
}
};
(async () => {
for await (const item of idMaker) {
console.log(item)
}
})()
异步生成器
function * createIdMaker (max) {
for (let i = 1; i <= max; i++) {
yield Promise.resolve(i)
}
};
(async () => {
const idMaker = createIdMaker(10)
for await (const item of idMaker) {
console.log(item)
}
})()
ES 2019
变化 | 描述 |
---|---|
数组扩展方法 | flat()、flatMap() |
对象扩展方法 | Object.fromEntries() |
字符串扩展方法 | trimStart()、trimEnd()、 |
数组排序增强 | 稳定排序,确保每次排序相同 |
Symbol数据增强 | 新增description属性 |
try-catch语法块增强 | try-catch中catch 参数可省略 |
es2019-数组扩展方法
// flat()
// 创建一个新数组,其中所有子数组元素都以递归方式连接到该数组中,直到达到指定的深度。
// 即数组扁平化,可指定数组扁平化的深度
const arr1 = [0, 1, 2, [3, 4]];
console.log(arr1.flat());
// expected output: [0, 1, 2, 3, 4]
const arr2 = [0, 1, 2, [[[3, 4]]]];
console.log(arr2.flat(2));
// expected output: [0, 1, 2, [3, 4]]
console.log(arr2.flat(Infinity));
// expected output: [0, 1, 2, 3, 4]
// flatMap()
// 返回一个新数组,每个元素都是回调函数的结果,并且展平为1的深度。
let arr1 = ["it's Sunny in", "", "California"];
arr1.map(x => x.split(" "));
// [["it's","Sunny","in"],[""],["California"]]
arr1.flatMap(x => x.split(" "));
// ["it's","Sunny","in", "", "California"]
es2019-对象扩展方法
// Object.fromEntries(): 将键值对列表转换为对象。
const entries = new Map([
['foo', 'bar'],
['baz', 42]
]);
const obj = Object.fromEntries(entries);
console.log(obj);
// expected output: Object { foo: "bar", baz: 42 }
es2019-字符串扩展方法
// trimStart(): 从字符串的开头删除空格。
const greeting = ' Hello world! ';
console.log(greeting);
// expected output: " Hello world! ";
console.log(greeting.trimStart());
// expected output: "Hello world! ";
// trimEnd(): 从字符串末尾删除空格。
const greeting = ' Hello world! ';
console.log(greeting);
// expected output: " Hello world! ";
console.log(greeting.trimEnd());
// expected output: " Hello world!";
数组排序增强
// + 数组稳定排序 确保每次排序顺序相同
const arr = [
{ id: 1, value: 'A' },
{ id: 1, value: 'B' },
{ id: 1, value: 'C' },
{ id: 1, value: 'D' },
{ id: 1, value: 'E' },
{ id: 1, value: 'F' },
{ id: 1, value: 'G' },
{ id: 1, value: 'H' },
{ id: 1, value: 'I' },
{ id: 1, value: 'J' },
{ id: 4, value: 'K' },
{ id: 1, value: 'L' },
{ id: 1, value: 'B' },
{ id: 1, value: 'C' },
{ id: 1, value: 'D' },
{ id: 1, value: 'E' },
{ id: 1, value: 'F' },
{ id: 1, value: 'G' },
{ id: 1, value: 'H' },
{ id: 1, value: 'I' },
{ id: 1, value: 'J' },
{ id: 4, value: 'K' },
{ id: 1, value: 'L' },
]
// => 旧版本的 ES 排序过后的结果可能不固定
console.log(arr.sort(function (a, b) {
return a.id - b.id
}))
// + try...catch 参数可省略
try {
throw new Error()
} catch {
console.log('error')
}
Symbol数据增强
// 只读description属性是一个字符串,返回Symbol对象的可选描述。
Symbol('desc').toString(); // "Symbol(desc)"
Symbol('desc').description; // "desc"
Symbol('').description; // ""
Symbol().description; // undefined
try-catch语法块增强
// try-catch中catch 参数可省略
function isValidJSON(text) {
try {
JSON.parse(text);
return true;
} catch {
return false;
}
}
ES 2020
变化 | 描述 |
---|---|
运算符扩展 | 空值合并运算符、可选链运算符 |
模块动态导入 | 可通过动态导入实现按需加载 |
BigInt | 基本数据类型,用于创建大型数字 |
字符串扩展方法 | matchAll() |
Promise扩展方法 | allSettled() |
es2020-运算符扩展
// + 空值合并运算符 ==============================================================
function foo (options) {
// 只有 size = null 或者 undefined 才会走100
options.size = options.size ?? 100
console.log(options)
}
foo({ size: 0 })
// + 可选链运算符 ================================================================
const list = [
{
title: 'foo',
author: {
name: 'zce',
email: 'w@zce.me'
}
},
{
title: 'bar'
}
]
list.forEach(item => {
// 有数据时才会继续向下访问 没有则返回undefined
console.log(item.author?.name)
// => console.log(item.author && iten.author.name)
})
// obj?.prop
// obj?.[expr]
// arr?.[index]
// func?.(args)
模块动态导入
import增强可实现模块异步导入&&动态导入,导入函数返回一个promise对象
import(`./l10n/${currentUser.locale}`)
.then((locale) => app.l10n.use(locale))
.catch((err) => app.reportError(err))
// 更安全的方式
try {
app.l10n.use(await import(`./l10n/${currentUser.locale}`))
} catch (err) {
app.reportError(err)
}
BigInt
BigInt表示更大 的整数。 253 - 1NumberBigInt
// 定义方式
// 在一个完整数字面量后面加n 的方式定义一个BigInt ,如:10n,或者调用函数BigInt()。
const theBiggestInt = 9007199254740991n;
const alsoHuge = BigInt(9007199254740991);
typeof 1n === 'bigint'; // true
typeof BigInt('1') === 'bigint'; // true
0n === 0 // ↪ false
0n == 0 // ↪ true
es2020-字符串扩展方法
// matchAll(): 返回一个包含所有匹配正则表达式的结果和分组捕获组的迭代器。
const regexp = RegExp('foo[a-z]*','g');
const str = 'table football, foosball';
const matches = str.matchAll(regexp);
for (const match of matches) {
console.log(`Found ${match[0]} start=${match.index} end=${match.index + match[0].length}.`);
}
// expected output: "Found football start=6 end=14."
// expected output: "Found foosball start=16 end=24."
Promise扩展方法
// Promise.allSettled(): 返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象表格,每个对象表示对应的promise结果。
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];
Promise.allSettled(promises).
then((results) => results.forEach((result) => console.log(result.status)));
// expected output:
// "fulfilled"
// "rejected"
.size ?? 100
console.log(options)
}
foo({ size: 0 })
// + 可选链运算符 ================================================================
const list = [
{
title: 'foo',
author: {
name: 'zce',
email: 'w@zce.me'
}
},
{
title: 'bar'
}
]
list.forEach(item => {
// 有数据时才会继续向下访问 没有则返回undefined
console.log(item.author?.name)
// => console.log(item.author && iten.author.name)
})
// obj?.prop
// obj?.[expr]
// arr?.[index]
// func?.(args)
模块动态导入
import增强可实现模块异步导入&&动态导入,导入函数返回一个promise对象
import(`./l10n/${currentUser.locale}`)
.then((locale) => app.l10n.use(locale))
.catch((err) => app.reportError(err))
// 更安全的方式
try {
app.l10n.use(await import(`./l10n/${currentUser.locale}`))
} catch (err) {
app.reportError(err)
}
BigInt
BigInt表示更大 的整数。 253 - 1NumberBigInt
// 定义方式
// 在一个完整数字面量后面加n 的方式定义一个BigInt ,如:10n,或者调用函数BigInt()。
const theBiggestInt = 9007199254740991n;
const alsoHuge = BigInt(9007199254740991);
typeof 1n === 'bigint'; // true
typeof BigInt('1') === 'bigint'; // true
0n === 0 // ↪ false
0n == 0 // ↪ true
es2020-字符串扩展方法
// matchAll(): 返回一个包含所有匹配正则表达式的结果和分组捕获组的迭代器。
const regexp = RegExp('foo[a-z]*','g');
const str = 'table football, foosball';
const matches = str.matchAll(regexp);
for (const match of matches) {
console.log(`Found ${match[0]} start=${match.index} end=${match.index + match[0].length}.`);
}
// expected output: "Found football start=6 end=14."
// expected output: "Found foosball start=16 end=24."
Promise扩展方法
// Promise.allSettled(): 返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象表格,每个对象表示对应的promise结果。
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];
Promise.allSettled(promises).
then((results) => results.forEach((result) => console.log(result.status)));
// expected output:
// "fulfilled"
// "rejected"