关注「索引目录」公众号,获取更多干货。
当然,我们有文档。当然,我们可以用谷歌搜索一切——或者问问克劳德、GPT 或 Gemini。但这里有个小问题:在搜索某样东西之前,你首先需要知道它存在。
所以这次我决定介绍一些 ECMAScript 标准的新新增功能——这些功能是近年来推出的,并且在现代环境中已经可用。
这个话题对我来说并不陌生。早在 2019 年,我就在 meet.js Summit 上做过一个题为“超越 ES6——炒作之后会是什么?”(大概是这个意思😄)的演讲。如果你想回顾一下 2015 年至 2019 年的 ECMAScript 特性,或许还能在 YouTube 上找到当时的录像。
好了,介绍就到此为止。
下面列出的是我近年来最喜欢的一些现代 JavaScript 特性。我并没有列出所有特性,只列出了那些实用、有趣或功能强大的特性。
你会发现……它们都相互关联,构成了一个更大的模式。
📅 ES2022 — 现代 JavaScript 的基础
✨ 顶级await
它解决了什么问题:
一项不错的体验优化。在此之前,你无法await直接在模块的顶层使用它。你必须将所有代码都封装在一个异步函数中,才能加载配置或初始化数据。这虽然不是什么大问题,但说实话有点多余。
旧方法(额外样板代码):
async function init() {
const config = await fetchConfig();
startApp(config);
}
init();
现在:
const config = await fetchConfig();
startApp(config);
重要性:
更清晰的启动逻辑,更少的繁琐步骤,更易于阅读。
🔒 私有类字段 ( #)
它解决了什么问题:
说实话,JavaScript 从来没有真正意义上的私有类字段。我们只是假装它有,创造了一些奇怪的约定_privateVar,比如 `<type>`,它实际上根本不是私有的 😉(当然……除非你使用的是 TypeScript)。
现在:
class User {
#id;
constructor(id) {
this.#id = id;
}
}
user.#id尝试在类外部访问会引发错误。
重要性:
真正的封装。更安全的抽象,更少的意外修改。
🧠Error.cause
它解决了什么问题:
有多少次因为一个错误引发了另一个错误,而导致你浪费了半天时间进行调试,但这两个错误之间的联系却几乎无法追踪?
旧方法:覆盖错误或手动附加元数据。
现在:
throw new Error("Failed to load data", {
cause: originalError
});
重要性:
更好的调试和日志记录。您可以追踪完整的故障链,而无需猜测。
🎯Object.hasOwn()
它解决了什么问题:
以前,要检查一个对象是否真的具有某个属性,需要创建这样一个令人困惑的复杂函数:
旧方法:
Object.prototype.hasOwnProperty.call(obj, "key");
现在:
Object.hasOwn(obj, "key");
重要性:
语法更简洁,更易读,更少出现边界情况意外。
📍 .at()— 相对指数
它解决的问题:
经典的初级面试题:如何获取数组的最后一个元素?所有高于初级水平的人最终都学会了同样的蹩脚方法。
旧方法:
arr[arr.length - 1];
现在:
arr.at(-1);
重要性:
或许算不上革命性的,但绝对更清晰易懂。
📅 ES2023 — 不可变性升级
本次发布的核心理念只有一个:避免意外突变。
🧹toSorted()
问题:Array.sort()这个功能很棒……但它会修改原始数组。有人会忘记这一点——结果你的应用就一半都出问题了。其他人记得这一点,所以他们每次都手动复制数组。
旧方法:
[...arr].sort();
现在:
const sorted = arr.toSorted();
它改变了什么:
您可以获得一个排序后的副本,而无需修改原始数据。
重要性:
对状态管理和函数式代码风格至关重要。
🔁 toReversed()&toSpliced()
同样的理念:复制而不是改变。
arr.toReversed();
arr.toSpliced(2, 1);
重要性:
可预测性。不会因为共享同一个数组引用而意外破坏代码。
🔎 findLast()/findLastIndex()
问题:
我们之前有 `[ find]`,但如果想要最后一个匹配的元素呢?以前的解决方法……并不优雅,而且肯定会让初级程序员感到困惑。
旧方法:
[...arr].reverse().find(fn);
现在:
arr.findLast(fn);
重要性:
减少噪音,更清晰的意图——代码准确地表达了你的意思。
📅 ES2024 — 数据转换与异步控制
🧩Object.groupBy()
问题:
对数组进行分组通常意味着编写 reducer,而这些 reducer 看起来比问题本身还要复杂。
旧方法:
users.reduce((acc, user) => {
(acc[user.role] ??= []).push(user);
return acc;
}, {});
现在:
const grouped = Object.groupBy(users, u => u.role);
重要性:
大幅提升代码可读性。以前需要辅助函数才能实现的功能,现在只需一行代码即可完成。
⚡Promise.withResolvers()
问题:
创建外部resolve/reject处理程序一直很麻烦。
旧方法:
let resolve;
const promise = new Promise(r => resolve = r);
现在:
const { promise, resolve, reject } =
Promise.withResolvers();
重要性:
更清晰的异步编排——尤其适用于队列、事件或复杂流程。
📦 可调整大小的 ArrayBuffer
问题:
缓冲区过去的大小是固定的,这在处理流式或动态数据时令人沮丧——尤其是如果你是那些喜欢尝试边缘 JavaScript、工作进程或二进制数据的怪人之一(比如我😄)。
const buffer = new ArrayBuffer(8, {
maxByteLength: 16
});
重要性:
为高级应用场景提供更灵活的内存管理。
📅 ES2025 — 函数式 JavaScript 正式成为主流
🧠 迭代器助手
问题:
数组方法固然好用,但它们每一步都会创建中间数组。有时这是不必要的。
旧方法(会创建额外的数组):
const result = arr
.map(x => x * 2)
.filter(x => x > 5)
.slice(0, 3);
每一步都会分配一个新的数组。
现在(延迟处理):
const result = iterator
.map(x => x * 2)
.filter(x => x > 5)
.take(3)
.toArray();
它取代了:
手动生成器管道或性能密集型数组链。
重要性:
-
值是逐步处理的(惰性求值) -
减少分配 -
在大数据集上表现更佳 -
更函数式风格的管道
想想:要有流式传输的思维模式,而不是“构建另一个数组”。
🧩 新集合方法
问题:
更高级的集合逻辑总是需要自定义辅助函数或笨拙的数组转换。
旧方法:
const intersection = new Set(
[...a].filter(x => b.has(x))
);
现在:
a.intersection(b);
a.union(b);
a.difference(b);
重要性:
直接在语言中实现类似数学运算。减少样板代码,使意图更清晰。
🔐RegExp.escape()
问题:
安全性。根据用户输入构建正则表达式很容易破坏模式,甚至引入安全漏洞。
旧方法:
const safe = userInput.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const regex = new RegExp(safe);
现在:
const regex = new RegExp(RegExp.escape(userInput));
重要性:
无需每次都编写自己的辅助函数,即可更安全地创建正则表达式。
⚡Promise.try()
问题:
有时你想以相同的方式处理同步代码和异步代码——尤其是在同步函数可能会抛出异常的情况下。
现在:
await Promise.try(() => mightThrow());
重要性:
一切都将自动基于 Promise,从而简化错误处理流程。
🧊 Float16 支持
JavaScript 在处理数字方面一直有点笨拙——默认使用 64 位浮点数。我们已经有了这个功能Float32Array一段时间了,它已经很有用了,但现在 JS 更进一步。
const data = new Float16Array(1024);
这实际上意味着:
-
较小的数值表示(16 位) -
降低内存占用 -
在某些 GPU/ML 场景中,数据传输速度更快
重要性:
图形、WebGPU、机器学习和以性能为导向的工作负载都能从更紧凑的数据中受益。
🧭 大局观
如果缩小画面,你会注意到一个规律:
-
较少的突变 -
更清晰的意图 -
更安全的异步处理 -
更强大的数据处理功能
JavaScript 的变化不再是轰轰烈烈的革命性变革,
而是通过细微而实用的升级不断演进,这些升级悄然让日常代码更简洁、更易于理解。
关注「索引目录」公众号,获取更多干货。

