与码俱进:来一起尝尝ES2020新特性

528 阅读4分钟

2020年来了,ES2020也来了,新增的几个特性到底香不香,来一起细细品一下。

globalThis

我们在书写js的时候,全局this会因为运行环境不同而不同。如果运行环境是浏览器,关键字为window,如果是node环境,则全局this关键字为global;在方法内部可以用this本身指代this。如果希望代码在上述任意环境中都能够运行,就要我们去手动判断环境类型然后决定使用哪个关键字,像下面这样:

// 
const getGlobalThis = () => {
  if (typeof globalThis !== 'undefined') return globalThis;
  if (typeof self !== 'undefined') return self;
  if (typeof window !== 'undefined') return window;
  if (typeof global !== 'undefined') return global;
  if (typeof this !== 'undefined') return this;
  // 注意:这样判断仍有可能返回错误结果!
  throw new Error('Unable to locate global `this`');
};
const theGlobalThis = getGlobalThis();

是不是很麻烦,但是!聪明绝顶的程序员们是不允许这么麻烦而又不靠谱的事情存在的,所以globalThis应运而生。无论是浏览器,node或者其他什么环境,无论是class还是模块都可以用globalThis指定当前环境的全局this,是不是很惊喜?

应用了新特性的代码也许不需要使用全局this。Js模块中可以使用importexport功能替代繁琐的全局状态。但是globalThis对于一些polyfills 等还是很有用的。

支持状况:

  • Chrome 71+
  • Firefox65+
  • Safari 12.1+
  • Node 12+
  • Babel

Promise 组合方法

Es6中已经支持了promise 组合的两个静态方法Promise.allPromise.race; 现在有了两个新的提案:Promise.allSettledPromise.any。这样js就有了四种不同功能的promise组合方法。 下面一一介绍一下这四种方法

Promise.all

Promise .all用来提示是不是数组中所有的promises都已经fullfilled或者有任何一个rejects。

举个栗子:假如用户点击按钮之后,你需要下载一些样式。这个过程中所有的样式文件HTTP请求需要同时启动:

const promises = [
  fetch('/component-a.css'),
  fetch('/component-b.css'),
  fetch('/component-c.css'),
];
try {
  const styleResponses = await Promise.all(promises);
  enableStyles(styleResponses);
  renderNewUi();
} catch (reason) {
  displayError(reason);
}

仅仅当所有的样式文件都请求成功了才会刷新UI,任何文件请求失败都会展示错误信息,不用再等待其他请求结果。

支持状况:

  • Chrome 32+
  • Firefox29+
  • Safari 8+
  • Node 0.12+
  • Babel

Promise.race

Promise.race用来提示promises组合中任何一个返回成功或者失败的情况都会返回对应结果。

let performHeavyComputation=()=>new Promise((res,rej)=>{
// 假装大量计算耗费3秒
    setTimeout(()=>res('success'),3000)
})
let rejectAfterTimeout=(time)=>new Promise((res,rej)=>{
    setTimeout(()=>rej('Computation error'),time)
})
try {
  const result = await Promise.race([
    performHeavyComputation(),
    rejectAfterTimeout(2000),
  ]);
  console.log('result');
} catch (error) {
  console.log(error);
}

执行大量计算任务时可能会耗费很长时间,我们同时向数组中传入一个两秒后reject的promise。当计算任务成功后,会向下执行;如果两秒后还没有任何返回值,就会返回第二个promise的reject结果,展示error信息。

支持状况:

  • Chrome 32+
  • Firefox29+
  • Safari 8+
  • Node 0.12+
  • Babel

Promise.allSettled

Promise.allSettled用来提示promises组合中所有的返回状态,无论是fulfilled还是rejected。当你不关心promise的返回状态,而是关心整体的请求是不是执行完的时候,它很有用。

举个栗子,当进行一系列独立的API请求时,可以使用这个方法来确认是不是所有请求都已经执行完了,这样就可以执行后续取消加载状态操作。

const promises = [
  new Promise((res)=>{setTimeout(()=>res('done1')),1000}),
  new Promise((res,rej)=>{setTimeout(()=>rej('error2')),1500}),
  new Promise((res)=>{setTimeout(()=>res('done1')),2000}),
];
const start = new Date()
//假设一些请求成功了,一些失败了。
const result=await Promise.allSettled(promises);
// 根据最终返回状态执行取消加载状态操作。
const useTime = new Date() - start
console.log(result, `${end}秒`);
// ->(3) [{…}, {…}, {…}]
// 0: {status: "fulfilled", value: "done1"}
// 1: {status: "rejected", reason: "error2"}
// 2: {status: "fulfilled", value: "done1"}
// length: 3
// __proto__: Array(0)
// 2秒

上述代码在没有考虑是否返回成功或者失败的情况下,最长执行时间为2秒,最终我们打印结果也为2秒。

支持状况:

  • Chrome 76+
  • Firefox71+
  • Safari 13+
  • Node 12.9.0+
  • Babel

Promise.any(提案)

Promise.any用来提示任何一个promise fulfilled,这和promise.race有点相似,但是它并不关心是不是有promise rejected了,但是目前仅仅在提案阶段,并没有浏览器支持。

const promises = [
  fetch('/endpoint-a').then(() => 'a'),
  fetch('/endpoint-b').then(() => 'b'),
  fetch('/endpoint-c').then(() => 'c'),
];
try {
  const first = await Promise.any(promises);
  // 任意promise fulfilled.
  console.log(first);
  // → e.g. 'b'
} catch (error) {
  // 所有的promise都rejected.
  console.log(error);
}

上述代码检测并打印最早执行成功的promise的返回结果。当所有请求都失败才会执行catch回调。

BigInt: 任意精度整数类型

BigInt是js中新的数字类型-任意精度整数,应用BigInt类型,可以安全的存储和操作大于最大安全数值的数字(Number.MAX_VALUE)。

有时候我们在操作一些很大的数值的时候,例如较大的IDs和高精度时间戳,由于js的数字大小有限制,导致最终的计算结果出现偏差甚至bug。面对这些情况,以前的做法一般是将其转化为字符串,但是应用了BigInt之后,这些数据可以用数字方法正确呈现。

Js中数字存在一个最大安全整数Number.MAX_SAFE_INTEGER — 2**53-1,一旦大于这个数字,计算结果就失真了:

const max = Number.MAX_SAFE_INTEGER;
// → 9_007_199_254_740_991

首先在这个基础上+1

max + 1;
// → 9_007_199_254_740_992 ✅

可以看到返回结果是正确的,但是如果+2呢

max + 2;
// → 9_007_199_254_740_992 ❌

显然是错误的,任何超过安全数值的计算都会失败。所以这时候就该BigInt上场了。 在整数类型中添加后缀'n'就会得到BigInt类型。也可以使用BigInt(number)方法将Number转换为BigInt。BigInt(123) === 123n。上述问题我们运用BigInt解决的话:

BigInt(Number.MAX_SAFE_INTEGER) + 2n;
// → 9_007_199_254_740_993n ✅

当两个数相乘的时候:

1234567890123456789 * 123;
// → 151851850485185200000 ❌

显然结果是错误的,但是运用BigInt:

1234567890123456789n * 123n;
// → 151851850485185185047n ✅

这次的结果就是正确的。

安全数字对于number类型的封印在BigInt身上解除了。 因为Bigint是js的一种新的类型,所以可以用typeof来获取它的数据类型:

typeof 123;
// → 'number'
typeof 123n;
// → 'bigint'

也正是因为它是一种新的数据类型,所以BigInt并不严格等于Number。 在计算时尽量避免BigInt和Number类型的混用。

42n === BigInt(42);
// → true
42n == 42;
// → true
42n === 42;
// → false

但是这并不影响它转化为布尔值进行条件判断的功能:

if (0n) {
  console.log('if');
} else {
  console.log('else');
}
// →0n判断为假,所以打印'else'.

Dynamic import()

动态引用方法。来对比一下静态引用:

// 默认导出
export default () => {
  console.log('Hi from the default export!');
};
// 导出 `doStuff`
export const doStuff = () => {
  console.log('Doing stuff…');
};

Static import用法:

<script type="module">
  import * as module from './utils';
  module.default();
  // → 打印 'Hi from the default export!'
  module.doStuff();
  // → 打印 'Doing stuff…'
</script>

Dynamic import()引入了新的依托于函数的import方式。import(moduleSpecifier)在获取、实例化并解析所有的模块依赖之后,创建并返回一个promise,fulfilled状态下成功函数参数为当前引用模块命名空间对象,这和被引用模块本身是一致的。下面是用法:

<script type="module">
  const moduleSpecifier = './utils.mjs';
  import(moduleSpecifier)
    .then((module) => {
    module.default();
    // 打印 'Hi from the default export!'
    module.doStuff();
    // 打印 'Doing stuff…'
    });
</script>

因为import()返回的是promise,所以他也可以用async/await来代替then这种回调函数的形式:

<script type="module">
  (async () => {
    const moduleSpecifier = './utils.mjs';
    const module = await import(moduleSpecifier)
    module.default();
    // → logs 'Hi from the default export!'
    module.doStuff();
    // → logs 'Doing stuff…'
  })();
</script>

下面是一个Dynamic import()根据导航实现懒加载的例子:

<!DOCTYPE html>
<meta charset="utf-8">
<title>My library</title>
<nav>
  <a href="books.html" data-entry-module="books">Books</a>
  <a href="movies.html" data-entry-module="movies">Movies</a>
  <a href="video-games.html" data-entry-module="video-games">Video Games</a>
</nav>
<main>按需加载</main>
<script>
  const main = document.querySelector('main');
  const links = document.querySelectorAll('nav > a');
  for (const link of links) {
    link.addEventListener('click', async (event) => {
      event.preventDefault();
      try {
        const module = await import(`/${link.dataset.entryModule}.mjs`);
        // 模块导出 `loadPageInto`方法.
        module.loadPageInto(main);
      } catch (error) {
        main.textContent = error.message;
      }
    });
  }
</script>

动态引入的懒加载是非常有用的。相比于静态加载,它可以防止加载很多不必要的资源。

上述就是ES2020新增的几个特性,不管你觉得香不香,反正我觉得挺香的。如果有不对之处,欢迎指正,如果觉得有用,就给我个大拇指吧o( ̄▽ ̄)o

生活的一部分是工作,工作的一部分是解决问题取悦生活,所以好好生活,好好工作,好好热爱(●ˇ∀ˇ●)

参考