最全 ECMAScript 攻略之 ES2021-ES11

ES2020 是与 2020 年相对应的 ECMAScript 版本

String.protype.matchAll

场景:

  • 获取某个子字符串在原字符串中的匹配项
  • 以及匹配项的下标位置
  • 正则分组在原字符串中的匹配项

以前只能通过regex.exec循环取出

注意: 正则中必须加上’/g’ 否则会报错

// 老办法
var regex = /t(e)(st(\d?))/g;
var string = "test1test2test3";

var matches = [];
var match;

// regex.exec执行一次少一次
while ((match = regex.exec(string))) {
  matches.push(match);
}

console.log(matches);
// [
//   ["test1", "e", "st1", "1", index: 0, input: "test1test2test3"],
//   ["test2", "e", "st2", "2", index: 5, input: "test1test2test3"],
//   ["test3", "e", "st3", "3", index: 10, input: "test1test2test3"]
// ]
复制代码

matchAll()方法返回一个正则表达式在当前字符串的所有匹配

不过,它返回的是一个遍历器(Iterator),而不是数组。遍历器转为数组是非常简单的,使用...运算符和 Array.from()方法就可以了。

const string = "test1test2test3";
const regex = /t(e)(st(\d?))/g;

const newdata = string.matchAll(regex);

for (const match of newdata) {
  console.log(match);
}
// ["test1", "e", "st1", "1", index: 0, input: "test1test2test3"]
// ["test2", "e", "st2", "2", index: 5, input: "test1test2test3"]
// ["test3", "e", "st3", "3", index: 10, input: "test1test2test3"]

// 转为数组的方法一
[...newdata];

// 转为数组的方法二
Array.from(newdata);
复制代码

返回值解析

首先根据正则表达式的括号数量判断有几个分组,

const regex = /t(e)(st(\d?))/g 有三个括号,说明有三个分组

  1. 第一个分组: (e)
  2. 第二个分组: (st(\d?))
  3. 第三个分组: (\d?)

接下来分析结果["test1", "e", "st1", "1", index: 0, input: "test1test2test3"]

  1. 数组第一个元素 是整个匹配。
  2. 第二个元素 被(e)捕获。
  3. 第三个元素 是被(st(\d?))捕获。
  4. 第四个元素 是被(\d?)捕获。
  5. ‘index’ 是整个匹配(步骤 1)从零开始的索引。
  6. ‘input’ 属性是被解析的原始字符串。

详细内容参考ES 入门-matchAll

Dynamic import

在之前import命令是静态声明。它们接受字符串文字作为模块说明符

只能在模块的顶层,不能在代码块之中(比如,在if代码块之中,或在函数之中)

这样的设计,虽然有利于编译器提高效率,但也导致无法在运行时加载模块。在语法上,条件加载就不可能实现


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

import(specifier)函数,支持动态加载模块, import() 的参数 specifier,指定所要加载的模块的位置。

import 命令能够接受什么参数,import()就能接受什么参数,两者区别主要是后者为动态加载。

import()返回一个 Promise 对象

import()可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用。它是运行时执行,也就是说,什么时候运行到这一句,就会加载指定的模块

使用场景

按需加载: import()可以在需要的时候,再加载某个模块。

const someVariable = "user";

import(`./some-modules/${someVariable}.js`)
  .then((module) => {
    // 业务逻辑
    module.loadPageInto(main);
  })
  .catch((err) => {
    // 加载失败
  });
复制代码

条件加载:import()可以放在 if 代码块,根据不同的情况,加载不同的模块。

if (condition) {
  import('moduleA').then(...);
} else {
  import('moduleB').then(...);
}
复制代码

动态的模块路径:import()允许模块路径动态生成。

import(f())
.then(...);
复制代码

详细内容参考ES 入门-import

Promise.allSettled

Promise 中有四个主要的方法

name Description
Promise.allSettled 1. 只有等到所有实例都返回结果,不管是fulfilled还是rejected,实例才会结束
2. 有时候,我们不关心异步请求的结果,只关心所有的请求有没有结束即可使用
added in ES2020 ✅
Promise.all 1.只要任意一个 promise 失败,则返回失败的 promise .
2.当所有异步操作都成功后,才返回 promise,返回值组成一个数组
added in ES2015 ✅
Promise.race 只要任意一个 promise 的状态改变(不管成功 or 失败),那么就返回那个 promise added in ES2015 ✅
Promise.any 1.只要其中任意一个 promise 成功,就返回那个已经成功的 promise。
2.如果所有的 promises 都失败/拒绝,就返回一个失败的 promise
added in ES2021 ✅

Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束

有时候,我们不关心异步请求的结果,只关心所有的请求有没有结束。这时,Promise.allSettled()方法就很有用


settled这个单词是什么意思是:

字面是停止/停当/安定的意思, 即

当 promise 处于非pending状态,如fulfilled或者rejected。 那么这个 promise 就是settled


有如下场景: 遍历一个 promise 数组,然后想返回一个带有status字段的新数组(不管status代表 promise 的成功还是失败)

[
  {
    status: "rejected",
    reason: "SyntaxError: Unexpected token < in JSON at position 0",
  },
  { status: "fulfilled", value: { success: true, data: "...." } },
];
复制代码

如果使用Promise.all只能手写一个处理函数才行

function reflect(promise) {
  return promise.then(
    (v) => {
      return { status: "fulfilled", value: v };
    },
    (error) => {
      return { status: "rejected", reason: error };
    },
  );
}

const promises = [fetch("index.html"), fetch("https://does-not-exist/")];
const results = await Promise.all(promises.map(reflect));
const successfulPromises = results.filter((p) => p.status === "fulfilled");
复制代码

Promise.allSettled已经帮我们写好了这个处理函数

async function test() {
  const promises = [fetch("./index.html"), fetch("https://does-not-exist/")];
  const results = await Promise.allSettled(promises);

  // 过滤出成功的请求
  const successfulPromises = results
    .filter((p) => p.status === "fulfilled")
    .map((p) => p.value);

  // 过滤出失败的请求,并输出原因
  const errors = results
    .filter((p) => p.status === "rejected")
    .map((p) => p.reason);

  console.log(errors);
}

test();
复制代码

一个更真实的场景: 请求结束后关闭 loading 状态

如果使用Promise.all 还需要判断执行结果

const urls = [
  /* ... */
];
const requests = urls.map((x) => fetch(x)); // Imagine some of these will fail, and some will succeed.

// Short-circuits on first rejection, all other responses are lost
try {
  await Promise.all(requests);
  console.log(
    "All requests have completed; now I can remove the loading indicator.",
  );
} catch {
  console.log(
    "At least one request has failed, but some of the requests still might not be finished! Oops.",
  );
}
复制代码

使用Promise.alallSettledl 直接判断即可

// We know all API calls have finished. We use finally but allSettled will never reject.
Promise.allSettled(requests).finally(() => {
  console.log("所有请求已结束,并且我不关心是成功还是失败");
  // 关闭loading状态
  removeLoadingIndicator();
});
复制代码

globalThis

ES2020 之前获取不同环境的this需要如下封装

const getGlobalThis = () => {
  // 在 webworker 或 service worker 中
  if (typeof self !== "undefined") return self;

  // 在浏览器中
  if (typeof window !== "undefined") return window;

  // 在 Node.js 中
  if (typeof global !== "undefined") return global;

  // 独立的 JavaScript shell
  if (typeof this !== "undefined") return this;

  throw new Error("Unable to locate global object");
};
const theGlobalThis = getGlobalThis();

if (typeof theGlobalThis.setTimeout !== "function") {
  // 此环境中没有 setTimeout 方法!
}
复制代码

现在,globalThis 提供了一个标准的方式来获取不同环境下的全局 this 对象(也就是全局对象自身)

if (typeof globalThis.setTimeout !== "function") {
  // 此环境中没有 setTimeout 方法!
}
复制代码

详细内容参考MDN-globalThis

空位合并操作符(Nullish coalescing Operator)

提出原因: 弥补 逻辑或操作符(||)空字符串,0,NaN判断为假值的不足

// 无法获取 空字符串,0,NaN
function checkReturn0(test) {
  return test || "666";
}

// 稍显麻烦
function checkReturn0(test) {
  return test !== undefined && test !== null ? test : "666";
}

test ?? "666" 等价于 test !== undefined && test !== null ? test : "666"

// 终极解决
function checkReturn() {
  return test ?? "666";
}
复制代码

空值合并操作符(??)只有当左侧为

  • null
  • undefined

空值合并操作符(??) 会返回右侧的值

const baz = 0 ?? 42;
console.log(baz);
// expected output: 0
复制代码
// `null` 检查和默认赋值
let test1 = null;
let test2 = test1 ?? "";

console.log("null check", test2); // 输出空字符串 ""
复制代码
// `undefined` 检查和默认赋值
const test = undefined ?? "default";

console.log(test);
// expected output: "default"
复制代码
// 比较后返回

// bad ?
let test;
function checkReturn() {
  if (!(test === undefined)) {
    return test;
  } else {
    return callMe("test");
  }
}

// better ?
function checkReturn() {
  return test ?? callMe("test");
}
复制代码

注意:与逻辑或操作符(||)不同,||会在左侧操作数为假值时返回右侧操作数

逻辑或操作符(||)只有当左侧为:

  • 空字符串: ''``
  • NaN
  • 0
  • null
  • undefined

逻辑或操作符(||) 会返回有右侧的值

var a = "" || 1;
// 输出 1
console.log(a);
复制代码

使用可选链操作符-?.

在寻找树状结构深处的属性值时,通常必须检查中间节点是否存在:

如果不判断就会抛出某个变量未定义的错误 ❌

var street = user.address && user.address.street;
复制代码

同样,许多 API 返回一个对象或 null / undefined,并且可能仅在结果不为 null 时才想从结果中提取属性。

?. 也叫链判断运算符。它可以让我们读取深度嵌套在对象链中的属性值,而不必验证每个引用。当属性不存在时,表达式停止计算并返回 undefined

const travelPlans = {
  destination: "DC",
  monday: {
    location: "National Mall",
    budget: 200,
  },
};

// bad ?
const res =
  travelPlans &&
  travelPlans.tuesday &&
  travelPlans.tuesday.location &&
  travelPlans.tuesday.location.href;

// better ?
// 输出 undefined
const res1 = travelPlans?.tuesday?.location?.href;
复制代码

在 JS 中,?? 运算符被称为非空运算符。如果第一个参数不是 null/undefined(这里只有两个假值,但是 JS 中假值包含:未定义 undefined、空对象 null、数值 0、空数字 NaN、布尔 false,空字符串'',不要搞混了),将返回第一个参数,否则返回第二个参数。比如,

null ?? 5; // => 5
3 ?? 5; // => 3
复制代码

给变量设置默认值时,以前常用 ||逻辑或运算符,例如,

const prevMoney = 1;
const currMoney = 0;
const noAccount = null;
const futureMoney = -1;
function moneyAmount(money) {
  return money || `账户未开通`;
}
console.log(moneyAmount(prevMoney)); // => 1
console.log(moneyAmount(currMoney)); // => 账户未开通
console.log(moneyAmount(noAccount)); // => 账户未开通
console.log(moneyAmount(futureMoney)); // => -1
复制代码

上面我们创建了函数 moneyAmount,它返回当前用户余额。我们使用 || 运算符来识别没有帐户的用户。然而,当用户没有帐户时,这意味着什么?将无账户视为空而不是 0 更为准确,因为银行账户可能没有(或负)货币。在上面的例子中,|| 运算符将 0 视为一个虚假值,不应该包括用户有 0 美元的帐户。让我们使用?? 非空运算符来解决这个问题:

const currMoney = 0;
const noAccount = null;
function moneyAmount(money) {
  return money ?? `账户未开通`;
}
moneyAmount(currMoney); // => 0
moneyAmount(noAccount); // => `账户未开通`
复制代码

概括地说 ?? 运算符允许我们在忽略错误值(如 0 和空字符串)的同时指定默认值。

可选链操作符(Optional Chaining)

?. 也叫链判断运算符。它允许开发人员读取深度嵌套在对象链中的属性值,而不必验证每个引用。当引用为空时,表达式停止计算并返回 undefined。比如:

var travelPlans = {
  destination: "DC",
  monday: {
    location: "National Mall",
    budget: 200,
  },
};
console.log(travelPlans.tuesday?.location); // => undefined
复制代码

现在,把我们刚刚学到的结合起来

function addPlansWhenUndefined(plans, location, budget) {
  if (plans.tuesday?.location == undefined) {
    var newPlans = {
      plans,
      tuesday: {
        location: location ?? "公园",
        budget: budget ?? 200,
      },
    };
  } else {
    newPlans ??= plans; // 只有 newPlans 是 undefined 时,才覆盖
    console.log("已安排计划");
  }
  return newPlans;
}
// 对象 travelPlans 的初始值,来自上面一个例子
var newPlans = addPlansWhenUndefined(travelPlans, "Ford 剧院", null);
console.log(newPlans);
// => { plans:
// { destination: 'DC',
// monday: { location: '国家购物中心', budget: 200 } },
// tuesday: { location: 'Ford 剧院', budget: 200 } }
newPlans = addPlansWhenUndefined(newPlans, null, null);
// logs => 已安排计划
// returns => newPlans object
复制代码

上面的例子包含了我们到目前为止所学的所有运算符。现在我们已经创建了一个函数,该函数将计划添加到当前没有嵌套属性的对象 tuesday.location 中。

我们还使用了非空运算符来提供默认值。此函数将错误地接受像“0”这样的值作为有效参数。这意味着 budget 可以设置为零,没有任何错误。

const travelPlans = {
  destination: "DC",
  monday: {
    location: "National Mall",
    budget: 200,
  },
};

// bad ?
const res =
  travelPlans &&
  travelPlans.tuesday &&
  travelPlans.tuesday.location &&
  travelPlans.tuesday.location.href;

// better ?
// 输出 undefined
const res1 = travelPlans?.tuesday?.location?.href;
复制代码

BigInt primitive type

旧版本的 JS 标准最大的整数只能是 2^53— 即Number.MAX_SAFE_INTEGERMath.pow(2, 53)

如果无法准确计算大于2^53的数字的

现在使用BigInt 用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。

这是 ECMAScript 的又一种数据类型。

可以用在一个整数字面量后面加 n 的方式定义一个 BigInt ,如:10n,或者调用函数 BigInt()

const theBiggestInt = 9007199254740991n;

const alsoHuge = BigInt(9007199254740991);
// ↪ 9007199254740991n
复制代码
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!
复制代码

上面的例子可以说明,无法计算大于Number.MAX_SAFE_INTEGER的值,

这个时候就可以使用BigInt

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
复制代码

最后

文章浅陋,欢迎各位看官评论区留下的你的见解!

觉得有收获的同学欢迎点赞,关注一波!

good

往期文章

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享