ES9 (ES2018)
ES2018(ES9)引入了一些重要的新特性,尤其是对正则表达式、异步迭代和对象的增强。这些更新为开发者提供了更强大的工具来处理字符串匹配、异步数据流和对象操作,进一步提升了 JavaScript 的功能性和开发效率。
ES2018(ES9)引入了一些重要的新特性,尤其是对正则表达式、异步迭代和对象的增强。
- Asynchronous Iteration (for await...of)
- Rest/Spread Properties for Objects
- RegExp Named Capture Groups
- RegExp Lookbehind Assertions
- RegExp Unicode Property Escapes
- s (dotAll) Flag for Regular Expressions
- Promise.prototype.finally( )
1. Asynchronous Iteration (for await...of)
ES2018 添加了 for await...of 循环,用于迭代异步可迭代对象。这种语法允许你在异步流中逐步获取数据,非常适合处理异步数据流(如网络请求或读取文件流)。
一个基础的示例:
const asyncObj = {
async *[Symbol.asyncIterator]() {
yield 1;
yield 2;
yield 3;
},
};
async function proceeAsyncData(data) {
for await (let item of data) {
console.log(item);
}
}
proceeAsyncData(asyncObj);
实际应用场景举例:
假设我们开发一个从服务器上分页获取用户数据的应用,每次只能获取一页(如10个用户)的数据,直到所有数据加载完成。这个场景在实际开发中非常常见,尤其是在处理大数据集时。
/**
* 根据传入的页码数返回对应页码的用户数据
* @param {*} page 页码数
*/
async function fetchUser(page) {
// 假设每页10个用户
const pageSize = 10;
// 模拟服务器延迟
await new Promise((resolve) => setTimeout(resolve, 1500));
// 总共的用户数据
const totalUsers = 45;
// 计算当前页对应的起始值和结束值
const start = (page - 1) * pageSize;
const end = Math.min(start + pageSize, totalUsers);
// 如果没有更多的用户,就返回空数组
if (start >= totalUsers) return [];
// 生成当前页的用户数据
const users = Array.from({ length: end - start }, (_, i) => {
return {
id: start + i + 1,
name: `用户${start + i + 1}`,
};
});
return users;
}
// 异步生成器函数:分页获取所有的用户
async function* fetchAllUsers() {
let page = 1; // 一开始从第一页开始
let hasMore = true; // 是否还有更多的用户数据
while (hasMore) {
// 根据当前页码拿到对应的用户数据
const users = await fetchUser(page);
if (users.length === 0) {
hasMore = false;
} else {
yield users;
page++;
}
}
}
async function processAllUsers() {
let allUsers = [];
for await (let users of fetchAllUsers()) {
console.log(`当前页的用户数据:`, users);
allUsers = allUsers.concat(users);
}
console.log(`所用的用户数据:`, allUsers);
}
processAllUsers();
2. Rest/Spread Properties for Objects
在 ES2018 中,... 运算符(也称为"扩展运算符")可以用于对象。
- 通过 rest 运算符,可以将对象的其余属性提取到新对象中
- 通过 spread 运算符,可以将对象的属性合并到另一个对象中
rest示例:
const { a, b, ...rest } = { a: 1, b: 2, c: 3, d: 4, e: 5 };
console.log(a, b, rest); // 1 2 { c: 3, d: 4, e: 5 }
spread示例:
const o1 = {
a: 1,
b: 2,
};
const o2 = {
c: 3,
d: 4,
};
const merged = { ...o1, ...o2 };
console.log(merged);
3. RegExp Named Capture Groups
ES2018 增加了在正则表达式中使用 命名捕获组 的功能。通过命名捕获组,你可以给捕获的子字符串命名,便于后续处理和引用。
先看一下以前捕获组没有命名的例子:
// 没有使用命名捕获组
const regex = /(\d{4})-(\d{2})-(\d{2})/;
const result = regex.exec("2024-09-11");
console.log(result[0]); // '2024-09-11' (整个匹配的字符串)
console.log(result[1]); // '2024' (第一组捕获到的年份)
console.log(result[2]); // '09' (第二组捕获到的月份)
console.log(result[3]); // '11' (第三组捕获到的日期)
现在可以直接为捕获组添加命名了:
const regex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const result = regex.exec("2024-09-11");
console.log(result);
console.log(result.groups); // { year: '2024', month: '09', day: '11' }
console.log(result.groups.year); // '2024' (第一组捕获到的年份)
console.log(result.groups.month); // '09' (第二组捕获到的月份)
console.log(result.groups.day); // '11' (第三组捕获到的日期)
4. RegExp Lookbehind Assertions
ES2018 中,正则表达式支持后行断言,也称为"lookbehind assertions"。
正则表达式中的"断言"可以理解为对某个位置做出一个条件检查,但本身并不消耗字符。断言的本质是"在这里的前面/后面是否有某个东西",而不会将这个东西作为匹配结果的一部分。
快速上手示例:检查一个数字后面是否有 px 字符串
假设想匹配一个数字,但这个数字后面必须有 px,且你只想要这个数字,不包括 px 本身。这时我们可以使用正向前行断言来实现:
const reg = /\d+(?=px)/; //匹配数字,并且后面是px
console.log("123px".match(reg)); // [ '123', index: 0, input: '123px', groups: undefined ]
早期只支持 前行断言:
-
正向前行断言 (?=...):检查某个模式的 后面 是否有特定字符。
const reg = /\d+(?=px)/; -
负向前行断言 (?!...):检查某个模式的 后面 是否 没有 特定字符。
const reg = /\d+(?!px)/; //匹配数字,并且后面不是px
console.log("123ab".match(reg)); // [ '123', index: 0, input: '123ab', groups: undefined ]
从 ES2018 开始支持 后行断言:
-
正向后行断言
(?<=...):确保某个模式前面 是否有特定字符// 匹配数字,但是数字前面能有$
const reg = /(?<=\$)\d+/;
console.log("100$456".match(reg)); // [ '456', index: 4, input: '100$456', groups: undefined ] -
负向后行断言
(?<!...):确保某个模式前面 是否没有特定字符const reg = /(?<!\$)\d+/;
console.log("100$456".match(reg)); // [ '100', index: 0, input: '100$456', groups: undefined ]
5. RegExp Unicode Property Escapes
ES2018 为正则表达式引入了Unicode 属性转义,使得开发者可以通过 \p 和 \P 语法匹配具有特定 Unicode 属性的字符。这极大增强了处理多语言字符的能力。
- \p : 指定对应的 Unicode 字符(指定汉语、拉丁语、希腊语...)
- \P: 指定不能有对应的 Unicode 字符(相当于排除)
const regExp = /\p{Script=Greek}/u;
console.log("α".match(regExp)); // [ 'α', index: 0, input: 'α', groups: undefined ]
console.log("a".match(regExp)); // null
const regExp2 = /\p{Script=Han}/u;
console.log(regExp2.test("你好")); // true
const regExp3 = /\p{Emoji}/u;
console.log(regExp3.test("😄")); // true
6. s (dotAll) Flag for Regular Expressions
正则表达式中的 . 是一个通配符,用来匹配除换行符(\n)之外的任何单个字符。在 ES2018 之前,. 不能匹配换行符,因此当你需要匹配跨行的文本时,需要编写更复杂的正则表达式来处理换行符。
举个例子:
const regex = /foo.bar/;
const str = "foo\nbar";
console.log(regex.test(str)); // false
于是只能曲线救国,使用 [\s\S],其中 \s 匹配空白字符(包括换行符),\S 匹配非空白字符,这样可以让 . 的功能扩展到所有字符。
const regex = /foo[\s\S]bar/;
const str = "foo\nbar";
console.log(regex.test(str)); // true
ES2018 引入了 s 标志(也叫 dotAll 模式),让 . 能够匹配包括换行符在内的所有字符,从而简化了跨行文本的匹配。
const regex = /foo.bar/s;
const str = "foo\nbar";
console.log(regex.test(str)); // true
7. Promise.prototype.finally( )
ES2018 为 Promise 对象添加了 finally( ) 方法,它允许你在 Promise 被处理完(不论是成功还是失败)之后执行代码。无论 Promise 的结果是 resolve 还是 reject,finally( ) 中的代码都会执行。这非常适合进行清理操作。
fetch("./data.json")
.then((response) => response.json())
.catch((error) => console.error("Error:", error))
.finally(() => console.log("Finished fetching data."));
实际应用场景举例:数据加载时的网络请求 + 加载动画
function showLoading() {
console.log("loading...");
}
function hideLoading() {
console.log("hide loading...");
}
// 请求数据
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const isSuccess = Math.random() > 0.5;
if (isSuccess) {
resolve("数据加载成功");
} else {
reject("数据加载失败");
}
}, 2000);
});
}
function loadData() {
showLoading();
fetchData()
.then((data) => {
console.log("成功:", data);
})
.catch((err) => {
console.log("失败:", err);
})
.finally(() => {
// 无论请求成功还是失败都会执行
hideLoading();
});
}
loadData();
-EOF-