JavaScript 的演变:2023-2025 年的新特性解析

随着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,并同时获得 resolvereject 函数。

语法如下:

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,其中 Mapkey 由回调函数提供。

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年有望成为标准。

参考资料

原文链接

相关推荐
JiangJiang22 分钟前
🚀 Vue人看React useRef:它不只是替代 ref
javascript·react.js·面试
1024小神26 分钟前
在GitHub action中使用添加项目中配置文件的值为环境变量
前端·javascript
龙骑utr31 分钟前
qiankun微应用动态设置静态资源访问路径
javascript
Jasmin Tin Wei32 分钟前
css易混淆的知识点
开发语言·javascript·ecmascript
齐尹秦35 分钟前
CSS 列表样式学习笔记
前端
wsz777739 分钟前
js封装系列(一)
javascript
Mnxj39 分钟前
渐变边框设计
前端
用户76787977373242 分钟前
由Umi升级到Next方案
前端·next.js
快乐的小前端43 分钟前
TypeScript基础一
前端
北凉温华44 分钟前
UniApp项目中的多服务环境配置与跨域代理实现
前端