ES2020(ES11)新特性

  • 概述
  • 九大新特性截图-简介
  • 新特性详解
  • 1.String.prototype.matchAll :字符串正则匹配扩展-返回全部结果的迭代器
  • 2.import() :动态导入 / 按需导入
  • 3.BigInt :大整数
  • 使用方法
  • 注意点
  • 4.Promise.allSettled :完成所有的promise,不会被reject中断
  • 5.globalThis:统一全局对象名称
  • 6.for-in mechanics:标准化 for-in 输出顺序
  • 7.Optional Chaining:可选链运算符 / 链式短路符(?)
  • 注意点
  • 8.Nullish coalescing Operator :空值合并运算符
  • 9.import.meta :模块元信息
  • 参考资料


概述

本文主要介绍ES2020新增特性
JS新特性即 TC39 经一系列提案审核发布后的特性。
ES6(2015年)之后的新特性github地址:
https://github.com/tc39/proposals/blob/master/finished-proposals.md

九大新特性截图-简介

es 属性 in_es 属性 in

  1. String.prototype.matchAll:字符串正则匹配扩展-返回全部结果的迭代器
  2. import() :动态导入 / 按需导入
  3. BigInt :大整数
  4. Promise.allSettled :完成所有的promise,不会被reject中断
  5. globalThis :统一全局对象名称
  6. for-in mechanics :标准化 for-in 输出顺序
  7. Optional Chaining :可选链运算符 / 链式短路符
  8. Nullish coalescing Operator :空值合并运算符
  9. import.meta :模块元信息

新特性详解

1.String.prototype.matchAll :字符串正则匹配扩展-返回全部结果的迭代器

旧做法:在正则表达式匹配到多个目标时,使用循环依次取出做处理。

var regex = /t(e)(st(\d?))/g;
var string = 'test1test2';

string.match(regex); // gives ['test1', 'test2'] - 无法得到捕获命名组(?<>)

var matches = [];
var match;
while (match = regex.exec(string)) {
	matches.push(match);
}
matches; /* 得到了想要的目标,但是使用了循环 */
// [
//   ["test1", "e", "st1", "1", index: 0, input: "test1test2", groups: undefined],
//   ["test2", "e", "st2", "2", index: 5, input: "test1test2", groups: undefined]
// ]

新特性String.prototype.matchAll 得到结果的迭代器-不需要再使用循环

const string = 'test1test2';
// g 修饰符加不加都可以
const regex = /t(e)(st(\d?))/g;
string.matchAll(regex) //[RegExp String Iterator]
//因为得到的是迭代器,故可自行转换为数组
// 转为数组方法一
console.log([...string.matchAll(regex)])
// 转为数组方法二
console.log(Array.from(string.matchAll(regex)))
// [
//   ["test1", "e", "st1", "1", index: 0, input: "test1test2", groups: undefined],
//   ["test2", "e", "st2", "2", index: 5, input: "test1test2", groups: undefined]
// ]

2.import() :动态导入 / 按需导入

import语句是在编译时静态分析的,这样的设计,固然有利于编译器提高效率,但也导致无法在运行时加载模块。

旧做法:在文档树中插入script元素/ 使用webpack的 ensure进行代码分离

function importModule(url) {
  return new Promise((resolve, reject) => {
    const script = document.createElement("script");
    const tempGlobal = "__tempModuleLoadingVariable" + Math.random().toString(32).substring(2);
    script.type = "module";
    script.textContent = `import * as m from "${url}"; window.${tempGlobal} = m;`;

    script.onload = () => {
      resolve(window[tempGlobal]);
      delete window[tempGlobal];
      script.remove();
    };

    script.onerror = () => {
      reject(new Error("Failed to load module script with URL " + url));
      delete window[tempGlobal];
      script.remove();
    };

    document.documentElement.appendChild(script);
  });
}

新特性import()
动态加载

import(`./section-modules/${link.dataset.entryModule}.js`)
  .then((module) => {
    module.loadPageInto(main);
  })
  .catch((err) => {
    main.textContent = err.message;
  });

可以使用模版字符串:

import(`./language-packs / $ {navigator.language} .js`)

可以在async函数中使用:–根据业务情况进行解构赋值

async function es2020Test() {
    const moudle1 = await import('./moudle1.js');
    const {function1, function2} = await import('./moudle1.js');
}

3.BigInt :大整数

一种表示大于(2^53-1)的整数的方法,这是Javascript可以可靠地用Number原语表示的最大数字。
Number.MIN_SAFF_INTERGER 至 Number.MAX_SAFF_INTERGER,超出这个范围的整数计算或者表示会丢失精度。

const x = Number.MAX_SAFE_INTEGER;
// ↪ 9007199254740991, this is 1 less than 2^53
const y = x + 1;
// ↪ 9007199254740992, ok, checks out
const z = x + 2
// ↪ 9007199254740992, wait, that’s the same as above!
使用方法

(1)通过将n附加到整数的末尾

const theBiggestInt = 9007199254740991n;

(2)调用构造函数来创建BigInt

const alsoHuge = BigInt(9007199254740991);
// ↪ 9007199254740991n
const hugeButString = BigInt('9007199254740991');
// ↪ 9007199254740991n
注意点

(1)BigInt并不严格等于Number

0n === 0
// ↪ false
0n == 0
// ↪ true

(2)BigInts无法与Numbers运算。 而是将引发TypeError。

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

(3)BigInts也不能使用一元+转换为Numbers。 必须使用Number 。

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

(4)BigInt可以与字符串连接。

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

因此,建议继续使用Number作为仅会遇到253以下值的代码。
将BigInt保留用于期望值较大的情况。

(5)与BigInts一起使用时,数学库中的操作将引发错误,|也会发生错误。

Math.round(1n)
// ↪ TypeError: Cannot convert a BigInt value to a number
Math.max(1n, 10n)
// ↪ TypeError: Cannot convert a BigInt value to a number
1n|0
// ↪ TypeError: Cannot mix BigInt and other types, use explicit conversions

(6)推荐以字符串的形式入参,避免丢失精度

const badPrecision = BigInt(9007199254740993);
// ↪9007199254740992n
const goodPrecision = BigInt('9007199254740993');
// ↪9007199254740993n
const alsoGoodPrecision = 9007199254740993n;
// ↪9007199254740993n

(7)BigInt仅表示整数。 Number仅可靠地表示最大为253的整数。这意味着除法和转换为Number都可能导致舍入。

5n / 2n
// ↪ 2n
Number(151851850485185185047n)
// ↪ 151851850485185200000
BigInt(1.5)
// ↪ RangeError: The number 1.5 is not a safe integer and thus cannot be converted to a BigInt
BigInt('1.5')
// ↪ SyntaxError: Cannot convert 1.5 to a BigInt

(8)parseInt和parseFloat会将BigInt转换为Number并在此过程中失去精度。

parseFloat(1234n)
// ↪1234
parseInt(10n)
// ↪10
// precision lost!
parseInt(900719925474099267n)
// ↪900719925474099300

(9)BigInts无法序列化为JSON。 但是,有一些库(例如,granola )可以处理此问题。

const bigObj = {a: BigInt(10n)};
JSON.stringify(bigObj)
// ↪TypeError: Do not know how to serialize a BigInt

4.Promise.allSettled :完成所有的promise,不会被reject中断

Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。

旧版本的promsie.all
如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise 的结果

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 200, 'promise2'));
const promise3 = new Promise((resolve, reject) => setTimeout(reject, 100, 'promise3'));
const promises = [promise1, promise2,promise3];
Promise.all(promises)
.then((result)=>
    console.log('result',result))
.catch(error=>{
    console.log('error',error)
})
.finally(()=>{
    console.log('finally')
})
// error promise3
// finally

新特性Promise.allSettled
Promise.allSettled返回一个promise,这promise会在所有传入的promise resolve或reject之后触发。
参数:iterable(一个可迭代的对象,例如Array,其中每个成员都是Promise。)

Promise.allSettled(promises)
 .then((results) =>
 		results.forEach((result) =>
 			console.log(result)));
// { status: 'fulfilled', value: 3 }
// { status: 'rejected', reason: 'promise2' }
// { status: 'rejected', reason: 'promise3' }

5.globalThis:统一全局对象名称

Javascript在不同的环境中,顶层对象实现是不统一的。
在 Web 中,可以通过 window、self 或者 frames 取到全局对象,但是在 Web Workers 中,只有 self 可以。在 Node.js 中,它们都无法获取,必须使用 global。
在松散模式下,可以在函数中返回 this 来获取全局对象,但是在严格模式和模块环境下,this 会返回 undefined。

旧做法

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

新特性:globalThis

if (typeof globalThis.setTimeout !== 'function') {
  //  此环境中没有 setTimeout 方法!
}

6.for-in mechanics:标准化 for-in 输出顺序

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

7.Optional Chaining:可选链运算符 / 链式短路符(?)

为了访问深层嵌套的属性,我们需要写一个很长的&&链去检查每个属性是否存在。
如果不这么做,很可能会报程序异常 Uncaught TypeError: Cannot read property ‘xxx’ of undefined。
可选链运算符,在不确定是否存在的属性后面加上?,也可以使用在方法后面。

旧做法

var street = user.address && user.address.street;
var fooInput = myForm.querySelector('input[name=foo]')
var fooValue = fooInput ? fooInput.value : undefined

新特性

var street = user.address?.street
var fooValue = myForm.querySelector('input[name=foo]')?.value
if (myForm.checkValidity?.() === false) { // skip the test in older web browsers
    // form validation fails
    return;
}

总结:Optional Chaining运算符的拼写是 ?.
它可能出现在三个位置:静态属性获取;动态属性获取;方法

obj?.prop       // optional static property access
obj?.[expr]     // optional dynamic property access
func?.(...args) // optional function or method call
注意点

(1)不支持 import?.(‘foo’)
(2)不支持在构造函数中使用:new a?.()
(3)不支持模板字符串:a?.string (4)不支持属性定义:a?.b = c

8.Nullish coalescing Operator :空值合并运算符

获取对象的属性的时,通常希望提供默认值,如果该属性访问的结果为null或未定义。 当前,在JavaScript中典型方法是使用||操作符。
旧做法

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!'; // Potentially unintended. '' is falsy, result: 'Hello, world!'
const animationDuration = response.settings.animationDuration || 300; // Potentially unintended. 0 is falsy, result: 300
const showSplashScreen = response.settings.showSplashScreen || true; // Potentially unintended. false is falsy, result: true

使用 || 操作符 会导致 false 、0 , ’ '等属性值被覆盖掉,导致得不到期望结果

新特性:空值合并运算符(??)

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

不会覆盖false 、0 , ’ '等属性

9.import.meta :模块元信息

元属性import.meta,返回当前模块的元信息,其本身是个对象,原型为null。

import.meta只能在模块内部使用,如果在模块外部使用会报错;如果不加 type=“module”,控制台会报错:Cannot use ‘import.meta’ outside a module。

<script type="module" src="path/to/hamster-displayer.mjs" data-size="500"></script>

import.meta至少包含两个属性:

(1)import.meta.url
如果模块里面有一个数据文件data.txt,那么就可以用下面的代码,获取这个数据文件的路径。

new URL('test.txt', import.meta.url)

注意,Node.js 环境中,import.meta.url返回的总是本地路径,即是file:URL协议的字符串,比如file:///home/user/foo.js。

(2)import.meta.scriptElement
import.meta.scriptElement是浏览器特有的元属性,返回加载模块的那个script元素,相当于document.currentScript属性。
旧做法

// 检测当前正在执行脚本的 <script> 元素是否是以异步模式执行的。
if (document.currentScript.async) {
  console.log("Executing asynchronously");
} else {
  console.log("Executing synchronously");
}

如果当前正在执行的代码是被其他代码作为回调函数或者事件处理函数调用的,那么currentScript属性不会指向任何<script>元素,而是会返回null。这个属性只在脚本被解析后首次运行时有效,且调用此属性的脚本不能是 JavaScript 模块

新特性

// HTML 代码为
<script type="module" src="my-module.js" data-foo="abc"></script>
// my-module.js 内部执行下面的代码
import.meta.scriptElement.dataset.foo
// "abc"

如下代码:加载此模块时,无论其位置如何,都将加载同级文件hamsters.jpg,并显示图像。 可以使用用于导入图像的脚本元素来配置图像的大小。

(async () => {
  const response = await fetch(new URL("../hamsters.jpg", import.meta.url));
  const blob = await response.blob();

  const size = import.meta.scriptElement.dataset.size || 300;

  const image = new Image();
  image.src = URL.createObjectURL(blob);
  image.width = image.height = size;

  document.body.appendChild(image);
})();
<script type="module" src="path/to/hamster-displayer.mjs" data-size="500"></script>

参考资料

https://github.com/tc39/proposals/blob/master/finished-proposals.mdhttps://www.jianshu.com/p/416a0931e96chttps://blog.csdn.net/z591102/article/details/109289384?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-3&spm=1001.2101.3001.4242https://www.bookstack.cn/read/es6-3rd/spilt.10.docs-proposals.md