种草 ES2020 新特性,真的学不动了
- 2020 年 2 月 24 日
- 筆記
点击上方“程序员成长指北”,选择“置顶或者星标”
期待你的关注!
作者:李大雷
https://juejin.im/post/5e09ca40518825499a5abff7
这几年,Ecma TC39 一年一次更新 ECMAScript 规范标准,截止目前,以下特性已进入 finished 状态。现在带大家体验种草 ES2020 新特性。
一:Promise.allSettled
Promise.all 缺陷
都知道 Promise.all
具有并发执行异步任务的能力。但它的最大问题就是如果其中某个任务出现异常(reject
),所有任务都会挂掉,Promise
直接进入 reject
状态。
想象这个场景:你的页面有三个区域,分别对应三个独立的接口数据,使用 Promise.all
来并发三个接口,如果其中任意一个接口服务异常,状态是 reject,这会导致页面中该三个区域数据全都无法渲染出来,因为任何 reject
都会进入 catch 回调, 很明显,这是无法接受的,如下:
Promise.all([ Promise.reject({code: 500, msg: '服务异常'}), Promise.resolve({ code: 200, list: []}), Promise.resolve({code: 200, list: []}) ]) .then((ret) => { // 如果其中一个任务是 reject,则不会执行到这个回调。 RenderContent(ret); }) .catch((error) => { // 本例中会执行到这个回调 // error: {code: 500, msg: "服务异常"} })
Promise.allSettled 的优势
我们需要一种机制,如果并发任务中,无论一个任务正常或者异常,都会返回对应的的状态(fulfilled
或者 rejected
)与结果(业务 value
或者 拒因 reason
),在 then
里面通过 filter
来过滤出想要的业务逻辑结果,这就能最大限度的保障业务当前状态的可访问性,而 Promise.allSettled
就是解决这问题的。
Promise.allSettled([ Promise.reject({code: 500, msg: '服务异常'}), Promise.resolve({ code: 200, list: []}), Promise.resolve({code: 200, list: []}) ]) .then((ret) => { /* 0: {status: "rejected", reason: {...}} 1: {status: "fulfilled", value: {...}} 2: {status: "fulfilled", value: {...}} */ // 过滤掉 rejected 状态,尽可能多的保证页面区域数据渲染 RenderContent(ret.filter((el) => { return el.status !== 'rejected'; })); });
二:可选链
可选链
可让我们在查询具有多层级的对象时,不再需要进行冗余的各种前置校验。
日常开发中,我们经常会遇到这种查询
var name = user && user.info && user.info.name;
又或是这种
var age = user && user.info && user.info.getAge && user.info.getAge();
这是一种丑陋但又不得不做的前置校验,否则很容易命中 Uncaught TypeError: Cannot read property...
这种错误,这极有可能让你整个应用挂掉。
用了 Optional Chaining ,上面代码会变成
var name = user?.info?.name;
var age = user?.info?.getAge?.();
可选链中的 ?
表示如果问号左边表达式有值, 就会继续查询问号后面的字段。根据上面可以看出,用可选链可以大量简化类似繁琐的前置校验操作,而且更安全。
三:空值合并运算符
当我们查询某个属性时,经常会遇到,如果没有该属性就会设置一个默认的值。比如下面代码中查询玩家等级。
var level = (user.data && user.data.level) || '暂无等级';
在 JS 中,空字符串、0 等,当进行逻辑操作符判断时,会自动转化为 false。在上面的代码里,如果玩家等级本身就是 0 级, 变量 level 就会被赋值 暂无等级
字符串,这是逻辑错误。
var level; if (typeof user.level === 'number') { level = user.level; } else if (!user.level) { level = '暂无等级'; } else { level = user.level; }
来看看用空值合并运算符如何处理
// { // "level": 0 // } var level = `${user.level}级` ?? '暂无等级'; // level -> '0级'
用空值合并运算在逻辑正确的前提下,代码更加简洁。
空值合并运算符 与 可选链 相结合,可以很轻松处理多级查询并赋予默认值问题。
var level = user.data?.level ?? '暂无等级';
四:dynamic-import
按需 import
提案几年前就已提出,如今终于能进入 ES 正式规范。这里个人理解成 "按需" 更为贴切。现代前端打包资源越来越大,打包成几 M 的 JS 资源已成常态,而往往前端应用初始化时根本不需要全量加载逻辑资源,为了首屏渲染速度更快,很多时候都是按需加载,比如懒加载图片等。而这些按需执行逻辑资源都体现在某一个事件回调中去加载。
el.onclick = () => { import(`/path/current-logic.js`) .then((module) => { module.doSomthing(); }) .catch((err) => { // load error; }) }
当然,webpack 目前已很好的支持了该特性。
五:globalThis
JavaScript 在不同的环境获取全局对象有不同的方式,NodeJS 中通过 global
, Web 中通过 window
, self
等,有些甚至通过 this
获取,但通过 this
是及其危险的,this
在 JavaScript 中异常复杂,它严重依赖当前的执行上下文,这些无疑增加了获取全局对象的复杂性。
过去获取全局对象,可通过一个全局函数:
var getGlobal = function () { if (typeof self !== 'undefined') { return self; } if (typeof window !== 'undefined') { return window; } if (typeof global !== 'undefined') { return global; } throw new Error('unable to locate global object'); }; var globals = getGlobal(); // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/globalThis
而 globalThis
目的就是提供一种标准化方式访问全局对象,有了 globalThis
后,你可以在任意上下文,任意时刻都能获取到全局对象。
六:BigInt
JavaScript 中 Number
类型只能安全的表示-(2^53-1)
至 2^53-1
范的值,即 Number.MIN_SAFE_INTEGER
至 Number.MAX_SAFE_INTEGER
,超出这个范围的整数计算或者表示会丢失精度。
var num = Number.MAX_SAFE_INTEGER; // -> 9007199254740991 num = num + 1; // -> 9007199254740992 // 再次加 +1 后无法正常运算 num = num + 1; // -> 9007199254740992 // 两个不同的值,却返回了true 9007199254740992 === 9007199254740993 // -> true
为解决此问题,ES2020 提供一种新的数据类型:BigInt
。使用 BigInt
有两种方式:
- 在整数字面量后面加
n
。
var bigIntNum = 9007199254740993n;
- 使用
BigInt
函数。
var bigIntNum = BigInt(9007199254740); var anOtherBigIntNum = BigInt('9007199254740993');
通过 BigInt
, 我们可以安全的进行大数整型计算。
var bigNumRet = 9007199254740993n + 9007199254740993n; // -> -> 18014398509481986n bigNumRet.toString(); // -> '18014398509481986'
注意:
BigInt
是一种新的数据原始(primitive)类型。
typeof 9007199254740993n; // -> 'bigint'
- 尽可能避免通过调用函数
BigInt
方式来实例化超大整型。因为参数的字面量实际也是Number
类型的一次实例化,超出安全范围的数字,可能会引起精度丢失。
七:String.prototype.matchAll
The
matchAll()
method returns an iterator of all results matching a string against a regular expression, including capturing groups. ——MDN
思考下面代码:
var str = '<text>JS</text><text>正则</text>'; var reg = /<w+>(.*?)</w+>/g; console.log(str.match(reg)); // -> ["<text>JS</text>", "<text>正则</text>"]
可以看出返回的数组里包含了父匹配项,但未匹配到子项(group)。移除全局搜索符"g
"试试。
var str = '<text>JS</text><text>正则</text>'; // 注意这里没有全局搜素标示符"g" var reg = /<w+>(.*?)</w+>/; console.log(str.match(reg)); // 上面会打印出 /* [ "<text>JS</text>", "JS", index: 0, input: "<text>JS</text><text>正则</text>", groups: undefined ] */
这样可以获取到匹配的父项,包括子项(group),但只能获取到第一个满足的匹配字符。能看出上面无法匹配到<text>正则</text>
。
如果获取到全局所有匹配项,包括子项呢?
ES2020 提供了一种简易的方式:String.prototype.matchAll
, 该方法会返回一个迭代器。
var str = '<text>JS</text><text>正则</text>'; var allMatchs = str.matchAll(/<w+>(.*?)</w+>/g); for (const match of allMatchs) { console.log(match); } /* 第一次迭代返回: [ "<text>JS</text>", "JS", index: 0, input: "<text>JS</text><text>正则</text>", groups: undefined ] 第二次迭代返回: [ "<text>正则</text>", "正则", index: 15, input: "<text>JS</text><text>正则</text>", groups: undefined ] */
能看出每次迭代中可获取所有的匹配,以及本次匹配的成功的一些其他元信息。
参考资料
- finished-proposals[1]
- TC39 Proposals[2]
❤️ 看完三件事
大家好,我是koala,如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:
- 点个【在看】,或者分享转发,让更多的人也能看到这篇内容
- 关注公众号【程序员成长指北】,不定期分享原创&精品技术文章。
- 添加微信【 coder_qi 】。加入程序员成长指北公众号交流群。