解读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';
||
或运算符表达的意思是左侧表达式为null
、undefined
、''
、false
、0
,右侧表达式都会生效。但我们想要的只是在null
或undefined
的时候生效。
es2020
引入了新的Null判断运算符??
。它的行为类似||
,但是只有运算符左侧的值为null
或undefined
时,才会返回右侧的值。
与链判断运算符?.
配合使用。
const userName = list?.info?.base?.userName ?? 'userName';
可用于函数参数默认值的判断
register(a, b) {
b = b ?? 3;
}
与&&
、||
运算符一起使用时,需要用括号来表明优先级,要不会报错。优先执行括号括起来的部分
// 错误
a && b ?? c
// 正确
(a && b) ?? c
这个运算符的一个目的,就是跟链判断运算符?.
配合使用,为null
或undefined
的值设置默认值。
Babel插件转换
https://babeljs.io/docs/en/babel-plugin-proposal-nullish-coalescing-operator
import()
一种使用动态说明符异步导入模块的语法
import
命令会被 JavaScript 引擎静态分析,先于模块内的其他语句执行。引擎处理import
语句是在编译时,而不是运行时。也就是说import
和export
命令只能在模块的顶层,而不能在代码块中。
假如我们要实现根据判断引入模块,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()
类似于Node
的require
方法,区别主要是前者是异步加载,后者是同步加载。
适用场合
- 按需加载(比如点击时加载某个文件或者模块)
- 条件加载(比如if判断模块中)
- 动态的模块路径(比如模块路径是实时生成)
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};
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"]
}
参考链接:
- https://github.com/tc39/proposal-export-ns-from
- https://babeljs.io/docs/en/babel-plugin-proposal-export-namespace-from
BigInt
一个用于处理任意精度整数的新数字基元
JavaScript 所有数字都保存成 64 位浮点数,这给数值的表示带来了两大限制。
- 数值的精度:只能到 53 个二进制位(相当于 16 个十进制位),大于这个范围的整数,JavaScript 是无法精确表示的。
- 大于或等于2的1024次方的数值,JavaScript 无法表示,会返回Infinity。
// 超过 53 个二进制位的数值,无法保持精度
Math.pow(2, 53) === Math.pow(2, 53) + 1 // true
// 超过 2 的 1024 次方的数值,无法表示
Math.pow(2, 1024) // Infinity
为了更精确地表示没有位数限制的整数,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
或字符串rejected
。fulfilled
时,对象有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
- p1和p2实例状态都为
fulfilled
时,p的状态才会变成fulfilled
,此时p1、p2的返回值组成一个数组,传递给p的回调函数 - 只要p1、p2之中有一个被
rejected
,p的状态就变成rejected
,此时第一个被reject的实例的返回值,会传递给p的回调函数
Promise.allSettled
一旦结束,状态总是fulfilled
,不会变成rejected
(也就是永远进不到p的catch回调)。
Promise.race
其中一实例返回状态,p的状态就会发生改变,并且不会再变
- 返回值
Promise.all
返回数组,每一个成员都是实例对应的结果
Promise.allSettled
返回数组,数组的每一个成员都是对象,每个对象都有status
属性,该属性的值只可能是字符串fulfilled
或字符串rejected
。fulfilled
时,对象有value
属性,rejected
时有reason
属性,对应两种状态的返回值.
Promise.race
每一个实例对应的结果
- 应用场景
Promise.all
参数Promise实例中是否有被reject的实例,无法确定所有请求都已结束
Promise.allSettled
可以确定所有请求都已结束
Promise.race
只要有返回值,立马停止其他请求
String.prototype.matchAll()
返回一个正则表达式在当前字符串中所有的匹配
参考链接:
- https://es6.ruanyifeng.com/#docs/regex#String-prototype-matchAll
- 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作为顶层,在任何环境下都存在。