解读ES2020(ES11)新特性

简介

2015年6月正式发布ES6,也称为ES2015,此后的每一年都会进行部分内容进行修订并在6月发布对应年号的ES版本,比如ES2016~ES2020。ES2020是ECMAScript语言规范的第11版,也被称为es11。

ES2020特性(参考链接)

链合并运算符

链式判断运算符

参考链接

如果读取对象内部的某个属性,往往需要判断一下该对象是否存在,比如获取list.info.base.userName的值

// 错误写法,当某一层级值为null或undefined时,会报错
const userName = list.info.base.userName;
// 正确写法(我们常用的方式)
const userName = (list && list.info && list.info.base && list.info.base.userName) || 'userName';

要取的userName处于对象的第三层,需要三层&&判断才能取到值。
es2020引入链合并运算符,简化上面的写法。

const userName = list?.info?.base?.userName || 'userName';

链合并运算符,在调用的时候判断左侧的对象是否为null或undefined。如果是的,就不再往下运算,而是返回undefined。
三种用法:

  • obj?.prop // 对象属性
  • obj?.[expr] // 同上
  • func?.(...args) // 函数或对象方法的调用
    示例代码
// obj?.prop
let list = {
  info: {
    // base: {
    //     userName: 'eyunhua'
    // }
  }
}
const userName = list?.info?.base?.userName || 'userName'; // 判断?.左侧表达式是否为null或undefined,否,则继续往下运算
// func?.(...args)
funtion register(a, b) {
    console.log(a, b, 'register function');
}
this.register?.(1, 2); // register函数存在,则执行此函数,并且可传参
// obj?.\[expr\]
let hex = "#C0FFEE".match(/#([A-Z]+)/i)?.[0];
console.log(hex);

Babel插件转换

https://babeljs.io/docs/en/babel-plugin-syntax-optional-chaining

Null判断运算符

属性值为null或undefined时,指定默认值

参考链接

读取对象属性的时候,如果某个属性的值是null或undefined,有时候需要为它们指定默认值。常见做法是通过||运算符指定默认值。

const userName = (list && list.info && list.info.base && list.info.base.userName) || 'userName';

||或运算符表达的意思是左侧表达式为nullundefined''false0,右侧表达式都会生效。但我们想要的只是在nullundefined的时候生效。

es2020引入了新的Null判断运算符??。它的行为类似||,但是只有运算符左侧的值为nullundefined时,才会返回右侧的值。

与链判断运算符?.配合使用。

const userName = list?.info?.base?.userName ?? 'userName';

可用于函数参数默认值的判断

register(a, b) {
  b = b ?? 3;
}

&&||运算符一起使用时,需要用括号来表明优先级,要不会报错。优先执行括号括起来的部分

// 错误
a && b ?? c
// 正确
(a && b) ?? c

这个运算符的一个目的,就是跟链判断运算符?.配合使用,为nullundefined的值设置默认值。

Babel插件转换

https://babeljs.io/docs/en/babel-plugin-proposal-nullish-coalescing-operator

import()

一种使用动态说明符异步导入模块的语法

参考链接

import命令会被 JavaScript 引擎静态分析,先于模块内的其他语句执行。引擎处理import语句是在编译时,而不是运行时。也就是说importexport命令只能在模块的顶层,而不能在代码块中。

假如我们要实现根据判断引入模块,import命令是不可能实现的。

// 报错
if (page === 'home') {
  import Home from './home';
}

ES2020提案引入import()函数,支持动态加载模块

import(specifier) // specifier为加载模块的位置或者脚本文件地址

import()函数返回一个Promise对象,加载模块成功以后,这个模块会作为一个对象,当作then回调的参数。因此,可以使用对象解构赋值的语法,获取输出接口。

import(`./home.js`)  // home.js中export const export1 = ''; ....
  .then(({export1, export2})=> 
    // 加载成功的回调
  })
  .catch(err => {
    // 加载失败的回调
  });

import()是运行时执行,也就是说,什么时候运行到这一句,就会加载指定的模块。另外,import()函数与所加载的模块没有静态连接关系,这点也是与import语句不相同。import()类似于Noderequire方法,区别主要是前者是异步加载,后者是同步加载。

适用场合

  1. 按需加载(比如点击时加载某个文件或者模块)
  2. 条件加载(比如if判断模块中)
  3. 动态的模块路径(比如模块路径是实时生成)

Babel插件转换

https://babeljs.io/docs/en/babel-plugin-syntax-dynamic-import

export * as ns from 'module'

在模块内使用的专用语法

参考链接

在一个模块内,先输入模块再输出模块, import语句可与export语句写在一起。
注意:
写在一行后,实际上并没有导入接口,只是对接口进行了转发,导致在当前模块不能使用此接口。

export { foo, bar } from 'my_module';

// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };

es2020之前有一种import复合写法:

import * as someIdentifier from "someModule";

es2020引入对应的export写法:

export * as someIdentifier from "someModule";
// 等同于
import * as ns from "mod";
export {ns};

更多import和export介绍

Babel插件转换

安装插件@babel/plugin-proposal-export-namespace-from

npm install --save-dev @babel/plugin-proposal-export-namespace-from

在babel.cofig.js中添加plugin

{
  "plugins": ["@babel/plugin-proposal-export-namespace-from"]
}

参考链接:

  1. https://github.com/tc39/proposal-export-ns-from
  2. https://babeljs.io/docs/en/babel-plugin-proposal-export-namespace-from

BigInt

一个用于处理任意精度整数的新数字基元

参考链接

JavaScript 所有数字都保存成 64 位浮点数,这给数值的表示带来了两大限制。

  1. 数值的精度:只能到 53 个二进制位(相当于 16 个十进制位),大于这个范围的整数,JavaScript 是无法精确表示的。
  2. 大于或等于2的1024次方的数值,JavaScript 无法表示,会返回Infinity。
// 超过 53 个二进制位的数值,无法保持精度
Math.pow(2, 53) === Math.pow(2, 53) + 1 // true

// 超过 2 的 1024 次方的数值,无法表示
Math.pow(2, 1024) // Infinity

关于js中最大的安全整数为什么是2的53次方减1?

为了更精确地表示没有位数限制的整数,ES2020引入了不同于Number数字类型的BigInt数字类型, 只用来表示整数(大整数),没有位数的限制,任何位数的整数都可以精确表示。为了与 Number 类型区别,BigInt 类型的数据必须添加后缀n

const a = 2172141653n;
const b = 15346349309n;

// BigInt 可以保持精度
a * b // 33334444555566667777n

// 普通整数无法保持精度
Number(a) * Number(b) // 33334444555566670000

typeof运算符对于BigInt数据类型,返回bigint

typeof 1n // bigint

Bigint对象
参考链接

JavaScript 原生提供BigInt对象,可以用作构造函数生成 BigInt 类型的数值。转换规则基本与Number()一致,将其他类型的值转为 BigInt。

BigInt(123) // 123n
BigInt('123') // 123n
BigInt(false) // 0n
BigInt(true) // 1n

继承的方法

// 继承自其他对象的方法
BigInt.prototype.toString()
BigInt.prototype.valueOf()
BigInt.prototype.toLocaleString()

新增方法
BigInt.asUintN(width, BigInt): 给定的 BigInt 转为 0 到 2width - 1 之间对应的值。
BigInt.asIntN(width, BigInt):给定的 BigInt 转为 -2width - 1 到 2width - 1 - 1 之间对应的值。
BigInt.parseInt(string[, radix]):近似于Number.parseInt(),将一个字符串转换成指定进制的 BigInt。

转换运算
参考链接

数学运算
参考链接

其他运算
参考链接

作为vue props使用---暂不支持

已经在开发中,PR处于open状态

https://github.com/vuejs/vue/pull/11191

Babel插件转换

https://babeljs.io/docs/en/babel-plugin-syntax-bigint

Promise.allSettled

新的Promise组合器不会短路参考链接

该方法由 ES2020 引入。
接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束,一旦结束,状态总是 fulfilled,不会变成rejected。状态变成fulfilled后,Promise 的监听函数接收到的参数是一个数组,每个成员对应一个传入Promise.allSettled()Promise 实例。

const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);

const allSettledPromise = Promise.allSettled([resolved, rejected]);

allSettledPromise.then(function (results) {
  console.log(results);
});
// [
//    { status: 'fulfilled', value: 42 },
//    { status: 'rejected', reason: -1 }
// ]

监听函数接收到的数组,每个成员都是一个对象,对应传入Promise.allSettled()的两个 Promise 实例。每个对象都有status属性,该属性的值只可能是字符串fulfilled或字符串rejectedfulfilled时,对象有value属性,rejected时有reason属性,对应两种状态的返回值。

对比Promise.all

const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('hello');
    }, 1000);
}).then(result => result)
    .catch(e => e);
const p2 = new Promise((resolve, reject) => {
    throw new Error('报错了');
}).then(result => result);
    .catch(e => {
        console.log('p2:' + e);
    });
// const p = Promise.allSettled([p1, p2]);
const p = Promise.all([p1, p2]);

p.then(function (results) {
    console.log(results);
}).catch(err => {
    console.log('settled:' + err);
});

对比Promise.race

const p3 = new Promise((resolve, reject) => {
    // setTimeout(() => {
    resolve('成功了');
    // }, 2000);
});
const race = Promise.race([
    p3,
    new Promise(function (resolve, reject) {
        setTimeout(() => reject(new Error('request timeout')), 1000);
    })
]);

race.then(data => {
    console.log('success:', data);
}).catch(error => {
    console.log('error:', error);
});
相同点:

接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例

不同点:
  • 状态

Promise.all

  1. p1和p2实例状态都为fulfilled时,p的状态才会变成fulfilled,此时p1、p2的返回值组成一个数组,传递给p的回调函数
  2. 只要p1、p2之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数

Promise.allSettled

一旦结束,状态总是fulfilled,不会变成rejected(也就是永远进不到p的catch回调)。
Promise.race

其中一实例返回状态,p的状态就会发生改变,并且不会再变

  • 返回值

Promise.all

返回数组,每一个成员都是实例对应的结果

Promise.allSettled

返回数组,数组的每一个成员都是对象,每个对象都有status属性,该属性的值只可能是字符串fulfilled或字符串rejectedfulfilled时,对象有value属性,rejected时有reason属性,对应两种状态的返回值.

Promise.race

每一个实例对应的结果

  • 应用场景

Promise.all

参数Promise实例中是否有被reject的实例,无法确定所有请求都已结束

Promise.allSettled

可以确定所有请求都已结束

Promise.race

只要有返回值,立马停止其他请求

String.prototype.matchAll()

返回一个正则表达式在当前字符串中所有的匹配

参考链接:

  1. https://es6.ruanyifeng.com/#docs/regex#String-prototype-matchAll
  2. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/matchAll

ES2020之前

let regex = /t(e)(st(\d?))/g;
let string = 'test1test2test3';

let matches = [];
let match;
while (match = regex.exec(string)) {
  matches.push(match);
}
console.log(matches); // 返回正则表达式在当前字符串所有匹配(数组)

ES2020新增matchAll新特性

可一次性取出所有匹配,但是返回的是一个遍历器(Iterator),而非数组。可以用for of循环取出。
相对于返回数组,返回遍历器的好处在于,如果遍历结果是一个很大的数组,那么遍历器比较节省资源。
遍历器转换成数组,也是比较方便,可以使用...运算符和Array.from()就可以了。

let regex = /t(e)(st(\d?))/g;
let string = 'test1test2test3';
[...string.matchAll(regex)];
或
Array.from(string.matchAll(regex));

globalThis

一种在不同环境中获取顶层对象的通用方式参考链接

不同环境中获取全局对象的方法不同

  • 浏览器里面,顶层对象是window,但 Node 和 Web Worker 没有window。
  • 浏览器和 Web Worker 里面,self也指向顶层对象,但是 Node 没有self。
  • Node 里面,顶层对象是global,但其他环境都不支持

为了实现在不同环境中取到顶层对象,可采用下面三元表达式的方法实现。

// 方法一
(typeof window !== 'undefined'
   ? window
   : (typeof process === 'object' &&
      typeof require === 'function' &&
      typeof global === 'object')
     ? global
     : this);

// 方法二
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');
};

但这种方式相对比较麻烦、复杂。es2020引入globalThis作为顶层,在任何环境下都存在。

image.png

参考链接:

  • https://es6.ruanyifeng.com/
  • https://tc39.es/ecma262/#sec-intro
  • https://zhuanlan.zhihu.com/p/152245696?from_voters_page=true

你可能感兴趣的:(解读ES2020(ES11)新特性)