ECMAScript 2020新增特性
前言
TC39 委员会于近期批准了 ECMAScript 2020(即 ES2020)候选提案,即经审定最终添加到 JavaScript 语言中的特性集。ES2020 候选提案是今年六月提交 ECMA 大会(General Assembly)的审批汇总。
1. Promise.allSettled
Promise.all 缺陷
都知道 Promise.all 具有并发执行异步任务的能力。
先复习一下Promise.all 的用法
let p1 = Promise.resolve({
code: 200,
list: ["数据1"]
})
let p2 = Promise.resolve({
code: 200,
list: ["数据2"]
})
let p3 = Promise.resolve({
code: 200,
list: ["数据3"]
})
Promise.all([p1,p2,p3]).then((result) => {
// all方法并发的三个promise都是成功状态会执行
console.log(result);
/*
返回的结果: [{…}, {…}, {…}]
*/
}).catch((error) => {
console.log(error);
})
但时Promise.all 方法最大问题就是如果其中某个任务出现异常(reject),所有任务都会挂掉,Promise直接进入 reject 状态。
let p1 = Promise.resolve({
code: 200,
list: ["数据2"]
})
let p2 = Promise.reject({
code: 500,
errorMsg:"服务器错误"
})
let p3 = Promise.resolve({
code: 200,
list: ["数据3"]
})
Promise.all([p1,p2,p3]).then((result) => {
// 如果有一个promise 是reject,则不会执行这个回调函数
console.log(result);
}).catch((error) => {
console.log(error);
// 因为p2 是reject 所以会执行这个回到函数
// {code: 500,errorMsg:"服务器错误"}
})
如果小伙伴们在测试代码的时候,记得重新打开页面,不要刷新.
想象这个场景:在你的项目中,使用 Promise.all 来并发三个接口,每个接口都单独对应一个接口,请求数据, 如果其中任意一个接口服务异常,状态是reject,这会导致三个区域数据全都无法渲染出来,因为任何 reject 都会进入catch回调, 很明显,这是无法接受的.
Promise 在 ES2020 中新增了 Promise.allSettled() ,就能好的 帮我们解决这个问题,Promise.allSetted 方法,无论一个任务是正常或是异常,都会返回对应的状态(fulfilled或者rejected)与结果(value),
let p1 = Promise.resolve({
code: 200,
list: ["数据2"]
})
let p2 = Promise.reject({
code: 500,
errorMsg:"服务器错误"
})
let p3 = Promise.resolve({
code: 200,
list: ["数据3"]
})
Promise.allSettled([p1,p2,p3]).then((result) => {
// Promise.allSettled会在这里中处理并发promise状态和结果
console.log(result);
/**
返回的结果:
[
{status: "fulfilled", value: {…}}
{status: "rejected", reason: {…}}
{status: "fulfilled", value: {…}}
]
*/
})
这样我们在then方法中得到所有结果以后,我们就可以使用fill来过滤状态为rejected掉的数据
2. String.prototype.matchAll
String.prototype上的match()方法仅返回完全匹配,但是没有返回关于特定正则组的任意信息。String.prototype.matchAll可以返回比match()多很多的信息。返回的迭代器除了精确匹配外还给了我们访问所有的正则匹配捕获组。
还节的match方法吗?看下面一段代码
let str = "<div>this is div</div><p>this is JS</p>";
let reg = /<\w+>(.*?)<\/\w+>/g;
console.log(str.match(reg));
/**
返回结果:
["<div>this is div</div>", "<p>this is JS</p>"]
*/
我们都知道,match是可以正常匹配到所有匹配项,但却没办法匹配到子项(group)。如果想要匹配子项,那么需要把全局匹配 /g 标识去掉。
let str = "<div>this is div</div><p>this is JS</p>";
let reg = /<\w+>(.*?)<\/\w+>/;
console.log(str.match(reg));
/**
[
0: "<div>this is div</div>"
1: "this is div"
groups: undefined
index: 0
input: "<div>this is div</div><p>this is JS</p>"
length: 2
]
**/
这样可以获取到匹配的父项,包括子项(group),但只能获取到第一个满足的匹配字符。
如果既想要匹配所有匹配项,又想要匹配子项,那么 match() 是无法满足的。ES2020 提供了 matchAll() 方法.
注意matchAll方法返回的是迭代器,因此我们需要遍历获取结果
let str = '<div>this is div</div><p>this is JS</p>';
let reg = /<\w+>(.*?)<\/\w+>/g;
let allMatchs = str.matchAll(reg);
for(let match of allMatchs){
console.log(match)
}
/**
第一次遍历的结果:
[
0: "<div>this is div</div>"
1: "this is div"
groups: undefined
index: 0
input: "<div>this is div</div><p>this is JS</p>"
length: 2
]
第二次遍历的结果:
[
0: "<p>this is JS</p>"
1: "this is JS"
groups: undefined
index: 22
input: "<div>this is div</div><p>this is JS</p>"
length: 2
]
**/
3. import()
目前前端项目打包的资源越来越大,但应用初始化时资源并不需要全量加载,为了提高页面性能,往往需要按需加载资源。Domenic Denicola提案的动态导入可以实现按需加载。这个类似函数的格式(不是继承自Function .prototype)返回一个很强大的promise。使用场景比如: 按需导入,在一个脚本中计算模块名并加载执行变得可能。
element.onclick = () =>{
import("/js/helpers.js")
.then((module) =>{
console.log(module)
})
.chatch((err) => {
// load err
console.log(err)
})
}
因此我们也可以使用async异步函数来配置处理
element.onclick = async () =>{
let module = await import("/js/helpers.js")
// 处理 导入module模块
}
4. BigInt
JavaScript 中 Number 类型都保存为 64 位浮点数,精确度只能到 53 位,也就是说Js 中 Number类型只能安全的表示-(2^53-1)至 2^53-1 范的值,超出这个范围的整数计算或者表示会丢失精度。
console.log(Math.pow(2, 53)); // 9007199254740992
console.log(Math.pow(2, 53) + 1); // 9007199254740992
console.log(Math.pow(2, 53) === Math.pow(2, 53) + 1); // true
且无法正确表示大于或等于 2^1024 的数值。
console.log(Math.pow(2, 1023)); // 8.98846567431158e+307
console.log(Math.pow(2, 1024)); // Infinity
ES2020 中引入了新的数据类型 BigInt, 让Number.MAXSAFEINTEGER不再是JavaScript中的一个限制。BigInt是一个能表示任意精度整数的新基础类型。你可以通过使用BigInt方法或者在一个数字后添加n后缀来把一个数字转换为一个新的bigint类型。
// 使用BigInt
// 1\. 字面量方式在数字字面量后面加n
let num = 123n;
console.log(num); // 123n
console.log(typeof num); // bigint
// 2\. 函数执行的方式
let number = BigInt(Math.pow(2, 53) + 1);
console.log(number); // 9007199254740992n
console.log(typeof number); // bigint
那么我们看看BigInt处理大型数字相加处理
let number = BigInt(Math.pow(2, 53));
letnumber2 = BigInt(Math.pow(2, 53));
console.log(number); // 9007199254740992n
console.log(number2); // 9007199254740992n
letnum = number + number2;
console.log(num); // 18014398509481984n
console.log(num.toString()); // 18014398509481984
注意:
BigInt 是一种新的数据原始(primitive)类型。
5. for-in机制
ECMAScript遗留了一个关于for-in循环顺序的详细描述。在 ECMA-262 5rd Edition 中对遍历机制又进行了调整,并未并且规定具体的规则,不同浏览器有不同的实现,这导致对属性的遍历顺序存在不一致的问题。ES2020 中要求对象的遍历实现上,各浏览器要保持一致。
6. 可选链(Optional chaining)
以前在处理多层对象属性值获取的时候,通常需要对各层级的属性进行校验.
例如
let name = user && user.info && user.info.name
这是一种丑陋但又不得不做的前置校验,否则很容易命中 Uncaught TypeError: Cannot read property… 这种错误,这极有可能让你整个应用挂掉。
对此,ES2020 进行了优化,可以通过 ?. 来简化校验。
?. 操作符与 . 类似,两者的区别在于,?. 在获取对象属性时,如果其引用对象为 null 或 undefined,则表达式会发生短路,直接返回 undefined。
示例代码如下:
// 1\. 能正确找到属性值
let user = {
info:{
name:"小明"
}
}
var name = user?.info?.name;
console.log(name); // 小明
// 2\. 属性值不存在
let user2 = {
info:{
city:"上海"
}
}
var name2 = user2?.info?.name;
console.log(name2); // undefined
7. 空值合并运算符(Nullish coalescing Operator)
在JavaScript中我们经常会遇到给某个变量或者对象的属性添加默认值
示例:
// 三目运算符处理默认值
const name = user.name ? user.name : "默认名称";
// 逻辑运算符中的短路算法处理默认值
const name = user.name || "默认名称"
ES2020 新增了更简洁的空值合并操作符(??),左侧值为 null 或 undefined 时返回右侧默认值
const name = user.name ?? "默认名称"
但对于逻辑或操作符来说,''、0 都会转化为 false,容易产生逻辑错误。在业务上,大多是是想判断变量是否为 undefined 或者 null。
因此要注意,
// user.name的值会有可能会进行隐式类型转行,为0的时候,也会只用默认值
const name = user.name || "默认名称" ;
// ES2020新增的?? 运算符 有且仅当user.name的为null或者undefined时
// 才会启用默认值
const name = user.name ?? "默认名称"