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
有三个括号,说明有三个分组
- 第一个分组:
(e)
- 第二个分组:
(st(\d?))
- 第三个分组:
(\d?)
接下来分析结果["test1", "e", "st1", "1", index: 0, input: "test1test2test3"]
- 数组第一个元素 是整个匹配。
- 第二个元素 被
(e)
捕获。 - 第三个元素 是被
(st(\d?))
捕获。 - 第四个元素 是被
(\d?)
捕获。 - ‘index’ 是整个匹配(步骤 1)从零开始的索引。
- ‘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_INTEGER
或Math.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
复制代码
最后
文章浅陋,欢迎各位看官评论区留下的你的见解!
觉得有收获的同学欢迎点赞,关注一波!