随着Web技术的飞速发展,ECMAScript(简称ES)作为JavaScript的语言标准,也在不断进化。
本文将带你学习 ECMAScript 2023-2025 的新特性。
一、ECMAScript 2023 新特性
1.1 数组的扩展
Array.prototype.findLast()/Array.prototype.findLastIndex()
findLast()
从后向前遍历数组,并返回满足条件的第一个元素的值, 如果没有找到返回 undefined
。
ini
const list = [1, 2, 3, 4, 5];
console.log(list.findLast(element => element > 3)); // 输出: 5
console.log(list.findLast(element => element > 6)); // undefined
findLastIndex()
从后向前遍历数组,并返回满足条件的第一个元素的索引。若没有找到对应元素,则返回 -1
。
ini
const list = [1, 2, 3, 4, 5];
console.log(list.findLast(element => element > 3)); // 输出: 4
console.log(list.findLastIndex(element => element > 6)); // -1
Array.prototype.toSorted(compareFn)
返回一个按升序排序的新数组。与 sort
的区别是,sort 会直接修改原数组。
ini
const list = [5, 2, 4, 6, 1];
const arr = list.toSorted();
console.log(arr); // [1, 2, 4, 5, 6]
console.log(list); // [5, 2, 4, 6, 1]
Array.prototype.toReversed()
与 Array.prototype.reverse()
类似,该数组的元素顺序被反转,但不改变原始数组,而是返回一个新数组。
ini
const list = [1, 2, 3, 4, 5];
const arr = list.toReversed();
console.log(arr); // [5, 4, 3, 2, 1]
console.log(list); // [1, 2, 3, 4, 5]
Array.prototype.toSpliced()
与 Array.prototype.splice()
类似,在给定索引处删除和/或替换了一些元素,但不改变原始数组,返回一个新数组。
语法:
javascript
Array.prototype.toSpliced(start, deleteCount, ...items)
示例:
ini
const list = [1, 2, 3, 4, 5];
const arr = list.toSpliced(2, 0, 6);
console.log(list); // [1, 2, 3, 4, 5]
console.log(arr); // [1, 2, 6, 3, 4, 5]
Array.prototype.with(index, value)
替换给定参数 index 位置的数组元素,返回新数组,而不改变原始数组。
ini
const list = [1, 2, 3, 4, 5];
const arr = list.with(2, 6);
console.log(list); // [1, 2, 3, 4, 5]
console.log(arr); // [1, 2, 6, 4, 5]
1.2 WeakMap 扩展
WeakMap 支持 Symbol 作为键。
vbnet
const weak = new WeakMap();
const key = Symbol("ref");
weak.set(key, "ECMAScript");
console.log(weak.get(key)); // ECMAScript
1.3 Hashbang语法
ECMAScript 2023 中引入 Hashbang 语法,允许我们在代码中使用 shebang 表示法 (#!
),用于指定执行脚本时使用的解释器路径。
javascript
#!/usr/bin/env node
console.log('Hello from Hashbang Grammar!');
#!/usr/bin/env node
告诉操作系统使用哪个解释器来执行该脚本。在这个例子中,它指示使用/usr/bin/env命令来查找并执行node命令。
二、ECMAScript 2024 新特性
2.1 Promise
Promise.withResolvers
Promise.withResolvers()
允许创建一个新的 Promise,并同时获得 resolve
和 reject
函数。
语法如下:
arduino
const { promise, resolve, reject } = Promise.withResolvers();
返回值:
promise
:一个新的 Promise 实例。resolve
:对应的 resolve 函数。reject
:对应的 reject 函数。
需要手动控制Promise状态(如根据异步操作结果)时,可以使用 Promise.withResolvers()
。
javascript
const { promise, resolve, reject } = Promise.withResolvers();
setTimeout(() => {
resolve('成功');
}, 3000);
promise.then(result => console.log(result)); // 输出:成功
2.2 Object.groupBy() / Map.groupBy()
Map.groupBy()
方法允许开发者将可迭代对象分组为一个新的Map,其中 Map
的 key
由回调函数提供。
ini
Map.groupBy([0, -5, 3, -4, 8, 9], x => x < 0 ? -1 : 1)

Object.groupBy()
方法则生成一个对象而非Map,其工作原理与 Map.groupBy()
类似,不过返回一个对象。
ini
Object.groupBy([0, -5, 3, -4, 8, 9], x => x < 0 ? -1 : 1)

2.3 字符串扩展
String.prototype.isWellFormed()
isWellFormed()
让你能够测试一个字符串是否不包含单独代理项。
"单独代理项(lone surrogate)"是指满足以下描述之一的 16 位码元: 它在范围 0xD800 到 0xDBFF 内(含)(即为前导代理),但它是字符串中的最后一个码元,或者下一个码元不是后尾代理。 它在范围 0xDC00 到 0xDFFF 内(含)(即为后尾代理),但它是字符串中的第一个码元,或者前一个码元不是前导代理。
String.prototype.toWellFormed()
toWellFormed()
方法返回一个字符串,其中该字符串的所有单独代理项都被替换为 Unicode 替换字符 U+FFFD。
2.4 正则表达式
v 标志
RegExp 的 v
标志是 u
标志的超集,额外提供了两个功能。
- 支持根据字符串的 Unicode 属性进行匹配,通过
\p{...}
语法,开发者可以根据字符的 Unicode 属性来构造正则表达式。例如,\p{RGI_Emoji}
用于匹配任何表情符号,而\p{White_Space}
匹配所有空白字符。
javascript
const re = /^\p{RGI_Emoji}$/v;
// 匹配仅包含一个代码点的表情符号:
console.log(re.test('⚽')); // 输出: true ✅
// 匹配由多个代码点组成的表情符号:
console.log(re.test('??⚕️')); // 输出: true ✅
在上面的代码中,正则表达式 ^\p{RGI_Emoji}$
匹配任何单一表情符号,包括复杂的组合表情符号。这种功能使得正则表达式可以更加准确地处理各种 Unicode 字符。
- 设置符号:允许在字符类之间进行集合操作,这意味着可以使用
&&
操作符执行字符类的交集。例如,表达式[\p{White_Space}&&\p{ASCII}]
匹配既是空白字符又是 ASCII 字符的字符。
javascript
const re = /[\p{White_Space}&&\p{ASCII}]/v;
// 匹配常见的换行符:
console.log(re.test('\n')); // 输出: true
// 匹配其他 Unicode 空白字符,例如行分隔符:
console.log(re.test('\u2028')); // 输出: false
Temporal
Temporal 是一个全局对象,像 Math 一样位于顶级命名空间中,为 ECMAScript 语言带来了现代化的日期、时间接口。
Temporal API 类似 moment 时间库,提供了精确灵活的日期时间操作 API,从而更轻松地处理复杂的日期和时间。
比如加一天:
csharp
const date = Temporal.PlainDate.from("2024-08-14");
console.log(date.add({ days: 1 }).toString()); // Output: 2024-08-15
还在提案中
三、ECMAScript 2025 新特性
3.1 模式匹配
模式匹配,一种根据数据结构匹配并执行相应逻辑的机制,一个更强大的 switch 语句替代方案。
javascript
const user = { name: 'Jane', age: 25, isAdmin: false };
const status = match(user) {
case { name, age, isAdmin: true }: return `${name} is an admin`;
case { age, isAdmin: false }: return `${age}-year-old user`;
default: return 'Unknown status';
}
还在提案中 - ECMAScript Pattern Matching
3.2 数组与对象的扩展
新增了不可变数据结构 Record
(类似对象)和 Tuple
(类似数组)。
csharp
const record = #{ a: 1, b: 2 }; // Record
// 尝试修改会报错
record.name = "Bob"; // Error: Cannot assign to read-only property
const tuple = #[1, 2, 3]; // Tuple
提供可预测、不可变的数据结构。
应用场景
- 状态管理(如 Redux)。
- 数据传递(避免副作用)。
3.3 正则表达式
命名的捕获组
传统正则表达式中,只能使用普通捕获组(用索引访问)。
ini
const matched = '2025-04'.match(/(\d{4})-(\d{2})/)
const [, year, month] = matched;
匹配结果matched是一个数组,数组第一项是正则匹配的完整的字符串,从第二项开始就依次是捕获组的内容,所以year和month分别是数组第二项,和第三项。
在 ECMAScript 2018 的新特性中,我们可以使用 命名捕获组给正则表达式中的捕获组命名,这样可以更容易地通过名称来引用这些捕获组而不是通过索引。
在正则表达式中使用命名捕获组,你可以在圆括号内使用 ?<组名>
。
sql
const matched = '2025-04'.match(/(?<year>\d{4})-(?<month>\d{2})/)
const {year, month} = matched.groups
正则修饰符
精准控制匹配规则。
在之前,如果希望能够匹配上,也就是忽略大小写,可以这样写:
bash
/hello World/i.test('Hello world') // true
但如果只需要对部分进行忽略大小写。比如只需 hello 忽略大小写,上面是无法满足的,不过现在可以了,可以这样使用:
bash
/(?i:h)ello World/.test('Hello World') // true
(?:)
是非捕获分组。
3.4 Promise
Promise.try()
Promise.try()
方法接受一个函数作为参数,这个函数可以是同步的也可以是异步的。如果该函数返回一个值,Promise.try
会返回一个解析为该值的Promise;如果该函数返回一个Promise,Promise.try
会返回该Promise并保持其状态;如果该函数抛出异常,Promise.try
会返回一个拒绝的Promise,并带有该异常作为拒绝原因。
语法如下:
csharp
Promise.try( callbackfn, ...args )
示例:
javascript
function doSomething(action) {
return Promise.try(action)
.then((result) => console.log(result))
.catch((error) => console.error(error))
.finally(() => console.log("Done"));
}
doSomething(() => "Sync result");
doSomething(() => {
throw new Error("Sync error");
});
doSomething(async () => "Async result");
doSomething(async () => {
throw new Error("Async error");
});
输出:
上面的 doSomething 方法,等价于之前的:
javascript
async function doSomething(action) {
try {
const result = await action();
console.log(result);
} catch (error) {
console.error(error);
} finally {
console.log("Done");
}
}
可在 Can I use 查看支持度。
3.5 集合的扩展
intersection(other)
:求交集,返回一个新集合,新集合的元素既在this集合中,也在other集合中union(other)
:求并集,返回一个新集合,新集合元素包含this和other中所有的元素difference(other)
:求差集,返回一个新集合,新集合元素在this集合中,但不在other集合中symmetricDifference(other)
:求对称差,返回一个新集合,新集合元素只在this集合中或只在other集合isSubsetOf(other)
:this集合是否为other集合的子集isSupersetOf(other)
:his集合是否为other集合的父集isDisjointFrom(other)
:this集合和other集合是否没有共同元素,也就是交集是否为空
可在 Can I use 查看支持度。
3.6 管道操作符
管道操作符 |>
允许将函数的输出直接作为下一个函数的输入,解决嵌套调用导致的代码可读性问题。
javascript
const a = 1;
function add(num) {
return num + 10
}
function substract(num) {
return num - 1;
}
const b = a |> add |> substract; // 10
应用场景
- 数据处理管道(如数据转换)。
- 函数式编程中的函数组合。
注:该提案在Babel中已实现实验性支持,2025年有望成为标准。
3.7 JSON模块
支持将 JSON 直接作为模块导入,类似于导入 JavaScript 或 CSS 的方式。
javascript
import config from './config.json' assert { type: 'json' };
// 动态导入也支持
const loadLocalization = async (locale) => {
const translations = await import(`./locales/${locale}.json`, {
assert: { type: 'json' }
});
return translations.default;
};
注:该提案在Babel中已实现实验性支持,2025年有望成为标准。