JavaScript:ECMAScript 2020中的新增功能

 

JavaScript即将推出令人兴奋的新功能!

即使新ECMAScript 2020(ES2020)语言规范的最终批准已经在六月,您也可以立即开始尝试一下!

处理模块

一些重要的创新涉及模块。其中,开发人员长期以来一直要求的功能是动态导入。但是,让我们按顺序进行详细介绍。

动态导入

当前的模块导入机制基于静态声明,如下所示:

import * as MyModule from "./my-module.js";

该语句有两个约束:
在当前模块的加载时间评估导入模块的所有代码
该模块的说明符("./my-module.js"在上面的示例中)是一个字符串常量,您不能在运行时更改它

这些约束阻止有条件或按需加载模块。同样,在加载时评估每个相关模块也会影响应用程序的性能。
新import()语句通过允许您动态导入模块来解决了这些问题。该语句接受模块说明符作为参数并返回promise。同样,模块说明符可以是任何返回字符串的表达式。这是个好消息,因为我们现在可以在运行时加载JavaScript模块,如以下示例所示:

const baseModulePath = "./modules";
const btnBooks = document.getElementById("btnBooks");
let bookList = [];

btnBooks.addEventListener("click", async e => {
  const bookModule = await import(`${baseModulePath}/books.js`);

  bookList = bookModule.loadList();
});

该代码显示books.js了用户单击btnBooks按钮时如何正确加载模块。加载模块后,click事件处理程序将使用loadList()模块导出的功能。请注意如何通过字符串插值指定要导入的模块。
导入元数据
该import.meta对象提供当前模块的元数据。JavaScript引擎创建了它,其当前可用属性为url。此属性的值是从中加载模块的URL,包括任何查询参数或哈希。
例如,您可以使用该import.meta.url属性来构建data.json存储在当前模块相同文件夹中的文件的URL 。以下代码获得此结果:

const dataUrl = new URL("data.json", import.meta.url);

在这种情况下,会import.meta.url为URL该类提供data.json文件的基本URL 。

新的导出语法

importECMAScript 2015规范引入的声明为您提供了多种形式的模块导入。以下是一些示例:

import {value} from "./my-module.js";
import * from "./my-module.js";

在某些情况下,您可能需要导出从另一个模块导入的对象。方便的export语法可能会对您有所帮助,如下所示:

export {value} from "./my-module.js";
export * from "./my-module.js";

从开发人员的经验来看,导入和导出语句之间的这种对称性很方便。但是,在这些新规范之前不支持特定情况:

import * as MyModule from "./my-module.js";

要导出MyModule名称空间,应使用两个语句:

import * as MyModule from "./my-module.js";
export {MyModule};

现在,您可以通过一条语句获得相同的结果,如下所示:

export * as MyModule from "./my-module.js";

这种添加简化了您的代码,并使import和export语句之间保持对称。

数据类型和对象

新的ES2020规范引入了新的数据类型,标准化的全局对象以及一些简化开发人员生活的方法。让我们来看看。

BigInt和任意精度整数

如您所知,JavaScript只有一种数据类型:数字Number。这种原始类型允许您表示64位浮点数。当然,它也表示整数,但是最大可表示值为2 53,对应于Number.MAX_SAFE_INTEGER常数。
在不涉及整数表示的内部细节的情况下,有些情况下您可能需要更高的精度。考虑以下情况:
与其他提供64位整数数据的系统(例如GUID,帐号或对象ID)进行交互
需要超过64位的复杂数学计算的结果

第一种情况的解决方法是将数据表示为字符串。当然,这种解决方法不适用于第二种情况。
新的BigInt数据类型旨在解决这些问题。您可以BigInt通过将字母简单地附加n到数字来表示文字,如以下示例所示:

const aBigInteger = 98765432123456789n;

您也可以像使用BigInt()构造函数一样使用Number()构造函数:

const aBigInteger = BigInt("98765432123456789");

typeof现在,运算符将"bigint"应用于BigInt值的字符串返回:

typeof aBigInteger      //output: "bigint"

请记住,Number并且BigInt是不同类型的,所以你不能将它们混合。例如,尝试将Number值添加到BigInt值会引发TypeError异常,如下图所示:

您必须使用构造函数将Number值显式转换为值。BigIntBigInt()

用于正则表达式的matchAll()方法

您可以通过多种方式获取给定正则表达式的所有匹配项。以下是这些方法之一,但是您可以使用其他方法:

const regExp = /page (\d+)/g;
const text = 'text page 1 text text page 2';
let matches;

while ((matches = regExp.exec(text)) !== null) {
  console.log(matches);
}

此代码通过迭代匹配变量中的所有page x实例text。在每次迭代时,该exec()方法都会在输入字符串上运行,并且您将获得如下结果:

["page 1", "1", index: 5, input: "text page 1 text text page 2", groups: undefined]
["page 2", "2", index: 22, input: "text page 1 text text page 2", groups: undefined]

该matchAll()方法String的对象,您可以得到相同的结果,但在更紧凑的方式和更好的性能。下面的示例使用此新方法重写以前的代码:

const regExp = /page (\d+)/g;
const text = 'text page 1 text text page 2';
let matches = [...text.matchAll(regExp)];

for (const match of matches) {
  console.log(match);
}

该matchAll()方法返回一个迭代器。前面的示例使用传播运算符将迭代器的结果收集到数组中。

全局对象

访问全局对象需要不同的语法,具体取决于JavaScript环境。例如,在浏览器中,全局对象是window,但是您不能在Web Worker中使用它。在这种情况下,您需要使用self。另外,在Node.js中,全局对象是global。
在编写旨在在不同环境中运行的代码时,这会导致问题。您可能使用了this关键字,但是它undefined在以严格模式运行的模块和函数中。
该globalThis对象提供了一种跨不同JavaScript环境访问全局对象的标准方法。因此,现在您可以以一致的方式编写代码,而不必检查当前的运行环境。但是请记住,要尽量减少使用全局项,因为这被认为是不好的编程习惯。

Promise.allSettled()方法

目前,JavaScript有两种方法来组合诺言:Promise.all()和Promise.race()。
两种方法都将一个promise数组作为参数。以下是使用示例Promise.all():

const promises = [fetch("/users"), fetch("/roles")];
const allResults = await Promise.all(promises);

Promise.all()当所有的诺言都实现时,返回一个诺言。如果至少一个诺言被拒绝,则返回的诺言被拒绝。最终承诺的拒绝原因与第一个拒绝的承诺相同。
当至少一个承诺被拒绝时,这种行为无法为您提供直接获得所有承诺结果的方法。例如,在上面的代码中,如果fetch("/users")失败并且相应的诺言被拒绝,您将没有一个简单的方法来知道的诺言fetch("/roles")是兑现还是被拒绝。要获得此信息,您必须编写一些其他代码。
新的Promise.allSettled()组合器将等待所有诺言的兑现,无论其结果如何。因此,以下代码可让您知道每个诺言的结果:

const promises = [fetch("/users"), fetch("/roles")];
const allResults = await Promise.allSettled(promises);
const errors = results
  .filter(p => p.status === 'rejected')
  .map(p => p.reason);

特别是,此代码使您知道每个被拒绝的承诺失败的原因。

新运营商

几个新的运算符将使在非常常见的操作中更容易编写和阅读代码。猜猜哪一个?

空合并运算符

您看过多少次并使用了以下表达式?

const size = settings.size || 42;

||当您尝试分配的默认值是null或时,通常使用运算符来分配默认值undefined。但是,这种方法可能会导致一些潜在的意外结果。
例如,size上面示例中的常量42也将在settings.sizeis的值时被赋值0。但是,当值的默认值也将被指定settings.size为""或false。
为了克服这些潜在的问题,现在您可以使用无效的合并运算符(??)。先前的代码如下:

const size = settings.size ?? 42;

这允许仅在值为is或时42将默认值分配给size常数。settings.sizenullundefined

可选链接

考虑以下示例:

const txtName = document.getElementById("txtName");
const name = txtName ? txtName.value : undefined;

您txtName将从当前HTML文档中获得带有其标识符的文本框。但是,如果文档中不存在HTML元素,则txtName常量将为null。因此,在访问其value属性之前,必须确保txtName不是null或undefined。
可选的链接运算符(?.)使您可以拥有更紧凑,更易读的代码,如下所示:

const txtName = document.getElementById("txtName");
const name = txtName?.value;

与前面的示例一样,该name常量的值将为txtName.valueif txtNameis notnull或undefined;; undefined除此以外。
在如下所示的复杂表达式中,此运算符的优点得到了更多的赞赏:

const customerCity = invoice?.customer?.address?.city;

您还可以将可选的链接运算符应用于动态属性,如以下示例所示:

const userName = user?.["name"];

此外,它也适用于函数或方法调用:

const fullName = user.getFullName?.();

在这种情况下,如果该getFullName()方法存在,则将执行该方法。否则,表达式返回undefined。

使用新功能

在整篇文章中,您对ES2020的新功能进行了概述,并且您可能想知道何时才能使用它们。
根据caniuse.com的资料,所有最近的主流浏览器(但Internet Explorer)已经支持ECMAScript 2020带来的新功能。但是,在撰写本文时,Safari并不支持新的BigInt数据类型和matchAll()方法。
在Node.js的最新版本,支持所有功能,以及包括动态导入的启用ECMAScript的模块。
最后,Babel和TypeScript等最受欢迎的编译器的最新版本也使您可以使用最新的ES2020功能。

旁:使用JavaScript进行Auth0身份验证
在Auth0,我们大量使用了全栈JavaScript来帮助客户管理用户身份,包括密码重置,创建,供应,阻止和删除用户。因此,毫无疑问,在JavaScript Web应用程序上使用我们的身份管理平台简直是小菜一碟。
Auth0提供了一个免费层,可以开始使用现代身份验证。签出,或在此处注册免费的Auth0帐户!
然后,转到Auth0信息中心的“应用程序”部分,然后单击“创建应用程序”。在显示的对话框上,设置应用程序的名称,然后选择“单页Web应用程序”作为应用程序类型:

创建应用程序后,单击“设置”,并记下分配给您的应用程序的域和客户端ID。另外,将“允许的回调URL”和“允许的注销URL”字段设置为将处理Auth0的登录和注销响应的页面的URL。在当前示例中,页面的URL将包含您要编写的代码(例如http://localhost:8080)。
现在,在您的JavaScript项目中,如下安装auth0-spa-js库:

npm install @auth0/auth0-spa-js

然后,在您的JavaScript应用中实现以下内容:

import createAuth0Client from '@auth0/auth0-spa-js';

let auth0Client;

async function createClient() {
  return await createAuth0Client({
    domain: 'YOUR_DOMAIN',
    client_id: 'YOUR_CLIENT_ID'
  });
}

async function login() {
  await auth0Client.loginWithRedirect();
}

function logout() {
  auth0Client.logout();
}

async function handleRedirectCallback() {
  const isAuthenticated = await auth0Client.isAuthenticated();

  if (!isAuthenticated) {
    const query = window.location.search;
    if (query.includes("code=") && query.includes("state=")) {
      await auth0Client.handleRedirectCallback();
      window.history.replaceState({}, document.title, "/");
    }
  }

  await updateUI();
}

async function updateUI() {
  const isAuthenticated = await auth0Client.isAuthenticated();

  const btnLogin = document.getElementById("btn-login");
  const btnLogout = document.getElementById("btn-logout");

  btnLogin.addEventListener("click", login);
  btnLogout.addEventListener("click", logout);

  btnLogin.style.display = (isAuthenticated ? "none" : "block");
  btnLogout.style.display = (isAuthenticated ? "block" : "none");

  if (isAuthenticated) {
    const username = document.getElementById("username");
    const user = await auth0Client.getUser();

    username.innerText = user.name;
  }
}

window.addEventListener("load", async () => {
  auth0Client = await createClient();

  await handleRedirectCallback()
});

将YOUR_DOMAIN和YOUR_CLIENT_ID占位符替换为您在Auth0信息中心中找到的域和客户端ID的实际值。
然后,使用以下标记创建UI:

<p>Welcome <span id="username"></span></p>
  <button type="submit" id="btn-login">Sign In</button>
  <button type="submit" id="btn-logout" style="display:none;">Sign Out</button>

您的应用程序已准备好通过Auth0进行身份验证!
请查看Auth0 SPA SDK文档,以了解有关使用JavaScript和Auth0进行身份验证和授权的更多信息。

 

上一篇:JavaScript之ES6快速入门


下一篇:【Socket】Java Socket编程基础及深入讲解