每年,JavaScript 都会引入众多新特性。本文就来看看,即将在 ECMAScript 2025 规范中出现的新功能!
注意:本文提到的这些新特性很多已经被主流浏览器支持!
图片
Promise.try
Promise.try 旨在解决JavaScript中处理可能返回Promise的函数时的一些不便之处,包括:
- 统一处理同步和异步函数::在实际编程中,经常遇到需要处理一个函数f,这个函数可能是同步的,也可能是异步的(即返回Promise)。传统上,为了统一处理这两种情况,开发者可能不得不使用如Promise.resolve().then(f)这样的模式,但这会导致即使f是同步函数,它也会在下一个事件循环中异步执行。
- 异常处理::对于可能抛出异常的函数,开发者希望有一种简洁的方式来捕获并处理这些异常。Promise.try提供了一种类似于try-catch语句的语义,使得异常处理更加直观和方便。
- 代码可读性和简洁性:传统的Promise处理方式(如Promise.resolve().then(f)或new Promise(resolve => resolve(f())))相对冗长且不易记忆。Promise.try提供了一种更加简洁和易于理解的语法,使得代码更加清晰和易于维护。
新的Promise方法try就是为了解决上述问题。这个方法接受一个函数f作为参数,并立即执行该函数。如果f是同步函数并返回一个值,则Promise.try会返回一个解析为该值的Promise。如果f是异步函数并返回一个Promise,则Promise.try会返回该Promise并保持其状态。如果f抛出异常,则Promise.try会返回一个拒绝的Promise,并带有该异常作为拒绝原因。
举个例子:
const f = () => {
console.log('Function f is executing');
return 42; // 假设这是一个同步函数,返回一个值
};
Promise.try(f).then(value => {
console.log('Received value:', value); // 输出: Received value: 42
});
console.log('This will execute before the Promise.try callback');
在这个示例中,函数f是同步的,并返回一个值42。使用Promise.try包装f后,可以在.then方法中接收到这个值,并且这个处理过程是异步的。但是,与Promise.resolve().then(f)不同的是,使用Promise.try时,如果f是同步函数,它会在当前事件循环中立即执行,而不是在下一个事件循环中。
另外,如果f是一个异步函数,比如返回一个Promise,那么Promise.try同样可以处理这种情况:
const asyncF = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve('Async value');
}, 1000);
});
};
Promise.try(asyncF).then(value => {
console.log('Received async value:', value); // 一秒后输出: Received async value: Async value
});
在这个示例中,函数asyncF是异步的,并返回一个在1秒后解析的Promise。使用Promise.try包装asyncF后,可以在.then方法中接收到这个异步值。
目前,Chrome、Edge、Firfox 的最新版本已支持该功能:
图片
全新 Set 方法
JavaScript 的内置Set类将新增一些方法,以便执行集合论中常见的操作,包括:
- Set.prototype.intersection(other):返回两个集合的交集。
- Set.prototype.union(other):返回两个集合的并集。
- Set.prototype.difference(other):返回第一个集合与第二个集合的差集。
- Set.prototype.symmetricDifference(other):返回两个集合的对称差。
- Set.prototype.isSubsetOf(other):判断第一个集合是否是第二个集合的子集。
- Set.prototype.isSupersetOf(other):判断第一个集合是否是第二个集合的超集。
- Set.prototype.isDisjointFrom(other):判断两个集合是否不相交。
举个例子:
// 创建两个Set实例
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);
// 交集: 返回两个集合的公共元素
const intersection = setA.intersection(setB); // Set {3, 4}
// 并集: 返回两个集合的所有元素,不重复
const union = setA.union(setB); // Set {1, 2, 3, 4, 5, 6}
// 差集: 返回第一个集合中有而第二个集合中没有的元素
const difference = setA.difference(setB); // Set {1, 2}
// 对称差: 返回在两个集合中的元素,但不返回同时存在于两个集合中的元素
const symmetricDifference = setA.symmetricDifference(setB); // Set {1, 2, 5, 6}
// 子集判断: 判断第一个集合是否是第二个集合的子集
const isSubset = setA.isSubsetOf(setB); // false
// 超集判断: 判断第一个集合是否是第二个集合的超集
const isSuperset = setA.isSupersetOf(setB); // false
// 不相交判断: 判断两个集合是否不相交
const isDisjoint = setA.isDisjointFrom(setB); // false
目前,这些新方法已被主流浏览器普遍支持:
图片
全新正则表达式修饰符
正则表达式引擎将新增一些模式修饰符,包括:
- i — 忽略大小写(Ignore Case)
- m — 多行模式(Multiline)
- s — 单行模式(Single-line),也称为“点全”模式
- x — 扩展模式(Extended mode)
举个例子:
// 这个正则表达式在匹配时忽略大小写,但只在第一个字符上忽略
const re1 = /^[a-z](?-i:[a-z])$/i;
console.log(re1.test("ab")); // true
console.log(re1.test("Ab")); // true
console.log(re1.test("aB")); // false
// 这个正则表达式在匹配时忽略大小写
const re2 = /^(?i:[a-z])[a-z]$/;
console.log(re2.test("ab")); // true
console.log(re2.test("Ab")); // true
console.log(re2.test("aB")); // false
导入属性
在 JavaScript 模块导入语句中将支持内联语法,允许指定模块属性,以便支持不同类型的模块。这些属性通过with关键字后跟一个对象字面量来指定,对象中可以包含不同的键值对,例如{ type: "json" }。这样可以提高安全性,防止服务器意外返回不同的MIME类型,导致代码被意外执行。
举个例子:
// 导入一个JSON模块
import json from "./foo.json" with { type: "json" };
// 动态导入一个JSON模块
import("foo.json", { with: { type: "json" } });
在这个例子中,import语句和import()函数都使用了with关键字来指定模块的类型。这告诉JavaScript环境,foo.json是一个JSON模块,而不是一个JavaScript模块。
另外,还将对export语句进行扩展,允许在导出模块时指定属性。例如:
// 导出一个模块,并指定其类型
export { val } from './foo.js' with { type: "javascript" };
JSON 模块
目前,JavaScript模块系统并不直接支持导入JSON文件,这导致开发者需要使用一些非标准的方法来导入JSON数据。在JavaScript 环境中,未来将以一种通用的方式导入JSON模块。以标准化JSON模块的导入过程,确保在所有符合ECMAScript规范的宿主环境中都能一致地处理JSON模块。
解决方案是利用导入属性来指示一个模块是JSON格式的。这意味着开发者可以使用以下语法来导入JSON模块:
import json from "./foo.json" with { type: "json" };
import("foo.json", { with: { type: "json" } });
在这个例子中,with { type: "json" }部分告诉JavaScript环境,foo.json是一个JSON模块,应该被解析为JSON对象,并且该对象成为模块的默认导出。
迭代
尽管迭代器是表示大型或可能是无限枚举数据集的有用方式,但它们缺乏一些辅助方法,使得它们不如数组和其他有限数据结构那样易于使用。这导致一些问题本可以通过迭代器更好地表示,但却不得不用数组或使用库来引入必要的辅助方法来解决。
迭代器原型上将引入一系列新方法,允许更通用的使用和消费迭代器,包括:
- .map(mapperFn):允许对迭代器返回的每个元素应用一个函数。
- .filter(filtererFn):允许跳过迭代器中未通过过滤器函数的值。
- .take(limit):返回一个迭代器,最多产生底层迭代器产生的给定数量的元素。
- .drop(limit):跳过底层迭代器产生的给定数量的元素,然后产生任何剩余的元素。
- .flatMap(mapperFn):返回一个迭代器,它产生的是应用映射函数到底层迭代器产生的元素所生成的迭代器的所有元素。
- .reduce(reducer [, initialValue ]):允许对迭代器返回的每个元素应用一个函数,同时跟踪Reducer的最新结果(memo)。
- .toArray():将非无限迭代器转换为数组。
- .forEach(fn):用于对迭代器执行副作用,接受一个函数作为参数。
- .some(fn):检查迭代器中的任何值是否与给定的谓词匹配。
- .every(fn):检查迭代器生成的每个值是否通过了测试函数。
- .find(fn):用于查找迭代器中第一个匹配的元素。
举个例子:
function* naturals() {
let i = 0;
while (true) {
yield i;
i += 1;
}
}
const result = naturals()
.map(value => value * value)
.take(3);
result.next(); // {value: 0, done: false}
result.next(); // {value: 1, done: false}
result.next(); // {value: 4, done: false}
result.next(); // {value: undefined, done: true}
正则表达式捕获组命名支持重复
目前,在 JavaScript正则表达式中有一个限制:命名捕获组的名称必须是唯一的。在某些情况下,开发者可能需要匹配具有多种格式的相同内容,例如日期,它们可能以YYYY-MM或MM-YYYY的形式出现。如果不允许重复使用组名,开发者就无法为这两种格式使用相同的组名来提取年份。
而未来将为不同的模式片段使用相同的组名,只要这些片段不是在同一选择分支中。这样,即使在复杂的正则表达式中,开发者也可以为相同类型的数据使用相同的组名,从而简化了正则表达式的编写和理解。
举个例子:
// 假设想匹配两种日期格式 YYYY-MM 或 MM-YYYY 并提取年份
let regex = /(?<year>[0-9]{4})-[0-9]{2}|[0-9]{2}-(?<year>[0-9]{4})/;
// 使用这个正则表达式匹配字符串
let str = "2023-10";
let match = str.match(regex);
// 无论日期格式如何,都能提取出年份
if (match) {
console.log(match.groups.year); // 输出: 2023
}