阅读 2059

你将会用到的ES2020新特性

前言


自从ES6发布之后,TC39这个负责开发ECMAScript标准的机构每年都会发布新特性。

今年ES2020也会被发布。网上搜了一圈相关的文章,要么内容不全,要么没注明出处,而官方文档纯英文不好阅读。遂有此文,本文中的新特性来自github上已结束的提案文档

好消息是,目前很多ES2020新特性已经在Chrome上实现了,在本文中,我们将详细介绍这些功能,并了解新特性升级的逻辑。

新功能概览

  1. Optional Chaining
  2. Nullish coalescing Operator
  3. globalThis
  4. Promise.allSettled
  5. String.prototype.matchAll
  6. BigInt
  7. for-in mechanics
  8. import()

详细介绍

可选链运算符 - Optional Chaining

有时候为了访问深层嵌套的属性,我们需要写一个很长的&&链去检查每个属性是否存在,代码如下:


var price = result && result.body && result.body.data && result.body.data.price;复制代码


事实上在日常开发中我们经常要这么做,而且如果我们不这么做,那么很可能导致程序异常。为了简化上述的代码,于是乎新增了可选链运算符,代码如下:


var price = result?.body?.data?.price;复制代码


可选运算符不仅能作用到属性上,也能作用到方法。


const result = {};
const price = result?.body?.data?.price;

const user = {};
const name = user?.getInfo?.().getName?.();

const respone = {};
const firstProduct = respone?.body?.data?.products?.[0];复制代码


空值合并运算符 - Nullish coalescing Operator

获取对象的属性的时候,我们经常需要为 null/undefined 的属性设置默认值。目前正常的情况下我们可以使用 || 运算符,例如:


var user = {};
var name = user.name || 'p9';复制代码


但是,这么做会导致false,0,空字符串等属性会被覆盖掉,这是我们不愿意看到的,这时候就可以使用空值合并运算符来避免。例如:


const response = {
  settings: {
    nullValue: null,
    height: 400,
    animationDuration: 0,
    headerText: '',
    showSplashScreen: false
  }
};

const undefinedValue = response.settings.undefinedValue ?? 'some other default'; // result: 'some other default'
const nullValue = response.settings.nullValue ?? 'some other default'; // result: 'some other default'
const headerText = response.settings.headerText ?? 'Hello, world!'; // result: ''
const animationDuration = response.settings.animationDuration ?? 300; // result: 0
const showSplashScreen = response.settings.showSplashScreen ?? true; // result: false复制代码

标准化的全局对象 - globalThis

在不同环境下是很难轻便的获取全局对象的。因为,在浏览器侧,全局对象可能是 window 或者 self 或者 this 或者 frames; 在 node,全局对象可以从 global 或者 this 获取; 在非严格模式下,方法里面的 this 指向全局对象,但是在严格模式下又是 undefined ;当然,我们也可以通过 Function('return this')() 获取全局对象,但是在某些CSP设置下的浏览器中又不能访问,例如Chrome Apps。这些问题就导致我们可以通过如下代码:


var getGlobal = function () {
    // the only reliable means to get the global object is
    // `Function('return this')()`
    // However, this causes CSP violations in Chrome apps.
    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');
};复制代码


所以我们需要一个标准的方式去获取不同环境下的全局对象:globalThis。例如:


function canMakeHTTPRequest() {
    return typeof globalThis.XMLHttpRequest === 'function';
}

console.log(canMakeHTTPRequest());
// expected output (in a browser): true
复制代码

Promise.allSettled

相信大家都知道,Promise.all 是可以并发执行多个任务的,但是 Promise.all 有个缺陷,那就是只要其中有一个任务失败了,就会直接进入 catch 的流程,想象一下,我们有两个请求,一个失败了,结果都挂了,这不是我们想要的。


const promise1 = new Promise((resolve) => setTimeout(resolve, 200, 'good'));
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'reject'));
const promises = [promise1, promise2];

Promise.all(promises).
then((ret)=>{
  // 不会触发
  debugger
})
  .catch((ret)=>{
  console.log(ret); // reject
})
  .finally(()=>{
  // 会触发,获取不到我们想要的内容
});复制代码


我们需要一个方法来保证如果一个任务失败了,其它任务还会继续执行,这样才能最大限度的保障业务的可用性

。这时候Promise.allSettled 出现了,Promise.allSettled 会返回一个 promise,这个返回的 promise 会在所有的输入的 promise resolve 或者 reject 之后触发。


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)));

// expected output:
// {status: "fulfilled", value: 3}
// {status: "rejected", reason: "foo"}
复制代码


String.prototype.matchAll

matchAll 的行为跟 match 有点像,但 matchAll 返回的是一个迭代器,通过这个迭代器我们可以快速的获取匹配的所有内容。

在了解matchAll之前先来看看 match 有什么问题。首先我们先来看一下代码:


const regex = /t(e)(st(\d?))/g;
const string = 'test1test2';
const results = string.match(regex);
console.log(results);
// → ['test1', 'test2']复制代码


执行完之后可以发现,匹配到的内容是 test1,test2。而把正则的全局匹配选项去掉之后,得到的内容却是:


['test1', 'e', 'st1', '2', index: 0, input: 'test1test2', groups: undefined]复制代码


如果需要 test2 相关的匹配,这时候就没有了,那么怎么获取到 test2 相关的匹配?在此之前,我们可以使用 replace ,当然,代码长了点:


var regex = /t(e)(st(\d?))/g;
var string = 'test1test2';
var matches = [];
string.replace(regex, function () {
    var match = Array.prototype.slice.call(arguments, 0, -2);
    match.input = arguments[arguments.length - 1];
    match.index = arguments[arguments.length - 2];
    matches.push(match);
    // example: ['test1', 'e', 'st1', '1'] with properties `index` and `input`
});
matches;
// ["test1", "e", "st1", "1", input: "test1test2", index: 0]
// ["test2", "e", "st2", "2", input: "test1test2", index: 5]
复制代码


为了简化上面又臭又长的代码,于是乎就有了 matchAll


let regexp = /t(e)(st(\d?))/g;
let str = 'test1test2';

let array = [...str.matchAll(regexp)];

console.log(array[0]);
// expected output: Array ["test1", "e", "st1", "1"]

console.log(array[1]);
// expected output: Array ["test2", "e", "st2", "2"]复制代码


BigInt

BigInt 是一种内置对象,它提供了一种方法来表示大于 253 - 1 的整数。这原本是 Javascript 中可以用 Number 表示的最大数字。

相关语法如下,我们可以通过在数字后面添加n或者通过 BigInt() 来创建 BigInt 变量。


const theBiggestInt = 9007199254740991n;

const alsoHuge = BigInt(9007199254740991);
// 9007199254740991n

typeof alsoHuge === 'bigint' // true复制代码


bigint 跟 number 一样,也支持+,*,-,**和%等运算符:


const previousMaxSafe = BigInt(Number.MAX_SAFE_INTEGER);
// ↪ 9007199254740991

const maxPlusOne = previousMaxSafe + 1n;
// ↪ 9007199254740992n
 
const theFuture = previousMaxSafe + 2n;
// ↪ 9007199254740993n, this works now!

const multi = previousMaxSafe * 2n;
// ↪ 18014398509481982n

const subtr = multi – 10n;
// ↪ 18014398509481972n

const mod = multi % 10n;
// ↪ 2n

const bigN = 2n ** 54n;
// ↪ 18014398509481984n

bigN * -1n
// ↪ –18014398509481984n复制代码


bigint 可以像平时一样跟 number 比较大小:


1n < 2
// ↪ true

2n > 1
// ↪ true

2 > 2
// ↪ false

2n > 2
// ↪ false

2n >= 2
// ↪ true复制代码


转换成布尔类型的时候,bigint 的行为跟 number 是一样的:


if (0n) {
  console.log('Hello from the if!');
} else {
  console.log('Hello from the else!');
}

// ↪ "Hello from the else!"

0n || 12n
// ↪ 12n

0n && 12n
// ↪ 0n

Boolean(0n)
// ↪ false

Boolean(12n)
// ↪ true

!12n
// ↪ false

!0n复制代码


bigint 可以像 number 那样转换成字符串的


1n + '2'
// ↪ "12"

'2' + 1n
// ↪ "21"复制代码


需要注意的点

bigint 跟 number不严格相等

0n === 0
// ↪ false

0n == 0
// ↪ true复制代码


bigint 不能跟 number一起运算

1n + 2
// ↪ TypeError: Cannot mix BigInt and other types, use explicit conversions

1n * 2
// ↪ TypeError: Cannot mix BigInt and other types, use explicit conversions复制代码


不能使用+把bigint转换成number


+1n
// ↪ TypeError: Cannot convert a BigInt value to a number

Number(1n)
// ↪ 1复制代码


for-in mechanics

以前在不同的引擎下for in循环出来的内容顺序是可能不一样的,现在标准化了。

异步加载模块 - import()

有时候我们需要动态加载一些模块,这时候就可以使用 import() 了

// logger.js
export default {
    log: function() {
        console.log(...arguments);
    }
};

import('./logger.js')
  .then((module)=>{
  module.default.log('p9');
})复制代码


当然,也可以使用await进行等待

let module = await import('/modules/my-module.js');复制代码

后记

授人以鱼不如授人以渔,以后要看ES的新特性,可以查看GitHub上的文档


参考资料

MDN

github.com/tc39/propos…

developers.google.com/web/updates…

blog.logrocket.com/5-es2019-fe…