【第1828期】种草 ES2020 新特性

作者:杨涛

前言

2020年了,学它。今日早读文章由去哪儿@杨涛投稿分享。

正文从这开始~~

这几年,Ecma TC39一年一次更新 ecmascript 规范标准,截止目前,以下特性已进入 finished 状态。现在带大家体验种草 ES2020 新特性。

一:Promise.allSettled

Promise.all 缺陷

都知道 Promise.all 具有并发执行异步任务的能力。但它的最大问题就是如果其中某个任务出现异常(reject),所有任务都会挂掉,Promise直接进入 reject 状态。

想象这个场景:你的页面有三个区域,分别对应三个独立的接口数据,使用 Promise.all 来并发三个接口,如果其中任意一个接口服务异常,状态是reject,这会导致页面中该三个区域数据全都无法渲染出来,因为任何 reject 都会进入catch回调, 很明显,这是无法接受的,如下:

  1. Promise.all([

  2. Promise.reject({code: 500, msg: '服务异常'}),

  3. Promise.resolve({ code: 200, list: []}),

  4. Promise.resolve({code: 200, list: []})

  5. ])

  6. .then((ret) => {

  7. // 如果其中一个任务是 reject,则不会执行到这个回调。

  8. RenderContent(ret);

  9. })

  10. .catch((error) => {

  11. // 本例中会执行到这个回调

  12. // error: {code: 500, msg: "服务异常"}

  13. })

我们需要一种机制,如果并发任务中,无论一个任务正常或者异常,都会返回对应的的状态(fulfilled 或者 rejected)与结果(业务value 或者 拒因 reason),在 then 里面通过 filter 来过滤出想要的业务逻辑结果,这就能最大限度的保障业务当前状态的可访问性,而 Promise.allSettled 就是解决这问题的。

  1. Promise.allSettled([

  2. Promise.reject({code: 500, msg: '服务异常'}),

  3. Promise.resolve({ code: 200, list: []}),

  4. Promise.resolve({code: 200, list: []})

  5. ])

  6. .then((ret) => {

  7. /*

  8. 0: {status: "rejected", reason: {…}}

  9. 1: {status: "fulfilled", value: {…}}

  10. 2: {status: "fulfilled", value: {…}}

  11. */

  12. // 过滤掉 rejected 状态,尽可能多的保证页面区域数据渲染

  13. RenderContent(ret.filter((el) => {

  14. return el.status !== 'rejected';

  15. }));

  16. });

二:可选链(Optional chaining)

可选链 可让我们在查询具有多层级的对象时,不再需要进行冗余的各种前置校验。

日常开发中,我们经常会遇到这种查询

  1. var name = user && user.info && user.info.name;

又或是这种

  1. var age = user && user.info && user.info.getAge && user.info.getAge();

这是一种丑陋但又不得不做的前置校验,否则很容易命中 Uncaught TypeError: Cannot read property... 这种错误,这极有可能让你整个应用挂掉。

用了 Optional Chaining ,上面代码会变成

  1. var name = user?.info?.name;

  2. var age = user?.info?.getAge?.();

可选链中的 ? 表示如果问号左边表达式有值, 就会继续查询问号后面的字段。根据上面可以看出,用可选链可以大量简化类似繁琐的前置校验操作,而且更安全。

三:空值合并运算符(Nullish coalescing Operator)

当我们查询某个属性时,经常会遇到,如果没有该属性就会设置一个默认的值。比如下面代码中查询玩家等级。

  1. var level = (user.data && user.data.level) || '暂无等级';

在JS中,空字符串、0 等,当进行逻辑操作符判时,会自动转化为 false。在上面的代码里,如果玩家等级本身就是 0 级, 变量 level 就会被赋值 暂无等级 字符串,这是逻辑错误。

  1. var level;

  2. if (typeof user.level === 'number') {

  3. level = user.level;

  4. } else if (!user.level) {

  5. level = '暂无等级';

  6. } else {

  7. level = user.level;

  8. }

来看看用空值合并运算符如何处理

  1. // {

  2. // "level": 0

  3. // }

  4. var level = `${user.level}级` ?? '暂无等级';

  5. // level -> '0级'

用空值合并运算在逻辑正确的前提下,代码更加简洁。

空值合并运算符 与 可选链 相结合,可以很轻松处理多级查询并赋予默认值问题。

  1. var level = user.data?.level ?? '暂无等级';

四:dynamic-import

按需 import 提案几年前就已提出,如今终于能进入ES正式规范。这里个人理解成“按需”更为贴切。现代前端打包资源越来越大,打包成几M的JS资源已成常态,而往往前端应用初始化时根本不需要全量加载逻辑资源,为了首屏渲染速度更快,很多时候都是按需加载,比如懒加载图片等。而这些按需执行逻辑资源都体现在某一个事件回调中去加载。

  1. el.onclick = () => {

  2. import(`/path/current-logic.js`)

  3. .then((module) => {

  4. module.doSomthing();

  5. })

  6. .catch((err) => {

  7. // load error;

  8. })

  9. }

当然,webpack目前已很好的支持了该特性。

五:globalThis

Javascript 在不同的环境获取全局对象有不通的方式,node 中通过 global, web中通过 window, self 等,有些甚至通过 this 获取,但通过 this 是及其危险的,this 在 js 中异常复杂,它严重依赖当前的执行上下文,这些无疑增加了获取全局对象的复杂性。过去获取全局对象,可通过一个全局函数

  1. var getGlobal = function () {

  2. if (typeof self !== 'undefined') { return self; }

  3. if (typeof window !== 'undefined') { return window; }

  4. if (typeof global !== 'undefined') { return global; }

  5. throw new Error('unable to locate global object');

  6. };


  7. var globals = getGlobal();


  8. // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/globalThis

而 globalThis 目的就是提供一种标准化方式访问全局对象,有了 globalThis 后,你可以在任意上下文,任意时刻都能获取到全局对象。

六:BigInt

Js 中 Number类型只能安全的表示-(2^53-1)至 2^53-1 范的值,即Number.MINSAFEINTEGER 至Number.MAXSAFEINTEGER,超出这个范围的整数计算或者表示会丢失精度。

  1. var num = Number.MAX_SAFE_INTEGER; // -> 9007199254740991


  2. num = num + 1; // -> 9007199254740992


  3. // 再次加 +1 后无法正常运算

  4. num = num + 1; // -> 9007199254740992


  5. // 两个不同的值,却返回了true

  6. 9007199254740992 === 9007199254740993 // -> true

为解决此问题,ES2020提供一种新的数据类型:BigInt。使用 BigInt 有两种方式:

在整数字面量后面加n。
  1. var bigIntNum = 9007199254740993n;

使用 BigInt 函数。
  1. var bigIntNum = BigInt(9007199254740);

  2. var anOtherBigIntNum = BigInt('9007199254740993');

通过 BigInt, 我们可以安全的进行大数整型计算。

  1. var bigNumRet = 9007199254740993n + 9007199254740993n; // -> -> 18014398509481986n


  2. bigNumRet.toString(); // -> '18014398509481986'

注意:

BigInt 是一种新的数据原始(primitive)类型。

  1. typeof 9007199254740993n; // -> 'bigint'

尽可能避免通过调用函数 BigInt 方式来实例化超大整型。因为参数的字面量实际也是 Number 类型的一次实例化,超出安全范围的数字,可能会引起精度丢失。

七:String.prototype.matchAll

思考下面代码

  1. var str = '<text>JS</text><text>正则</text>';

  2. var reg = /<\w+>(.*?)<\/\w+>/g;


  3. console.log(str.match(reg));

  4. // -> ["<text>JS</text>", "<text>正则</text>"]

可以看出返回的数组里包含了父匹配项,但未匹配到子项(group)。移除全局搜索符“g”试试。

  1. var str = '<text>JS</text><text>正则</text>';

  2. // 注意这里没有全局搜素标示符“g”

  3. var reg = /<\w+>(.*?)<\/\w+>/;

  4. console.log(str.match(reg));


  5. // 上面会打印出

  6. /*

  7. [

  8. "<text>JS</text>",

  9. "JS",

  10. index: 0,

  11. input:

  12. "<text>JS</text><text>正则</text>",

  13. groups: undefined

  14. ]

  15. */

这样可以获取到匹配的父项,包括子项(group),但只能获取到第一个满足的匹配字符。能看出上面无法匹配到 <text>正则 </text>

如果获取到全局所有匹配项,包括子项呢?

ES2020提供了一种简易的方式:String.prototype.matchAll, 该方法会返回一个迭代器。

  1. var str = '<text>JS</text><text>正则</text>';

  2. var allMatchs = str.matchAll(/<\w+>(.*?)<\/\w+>/g);


  3. for (const match of allMatchs) {

  4. console.log(match);

  5. }




  6. /*

  7. 第一次迭代返回:

  8. [

  9. "<text>JS</text>",

  10. "JS",

  11. index: 0,

  12. input: "<text>JS</text><text>正则</text>",

  13. groups: undefined

  14. ]


  15. 第二次迭代返回:

  16. [

  17. "<text>正则</text>",

  18. "正则",

  19. index: 15,

  20. input: "<text>JS</text><text>正则</text>",

  21. groups: undefined

  22. ]

  23. */

能看出每次迭代中可获取所有的匹配,以及本次匹配的成功的一些其他元信息。

参考资料

关于本文 作者:@blue_yang 原文:https://juejin.im/post/5e09ca40518825499a5abff7

为你推荐


【第1794期】一文详解Sass新特性 - 模块


【图书】深入理解JavaScript特性


【第1558期】ES5 to ESNext —  自 2015 以来 JavaScript 新增的所有新特性