JavaScript 开发涉及语言特性、运行时、框架、库和工程工具。本文分别介绍这些领域在原文发布时的变化。
语言有哪些新变化
ECMAScript 标准采用年度发布机制。
ECMAScript 2025
ECMAScript 2025 于 2025 年 6 月发布,完整规范已经公开。
迭代器辅助方法
迭代器可以直接使用 .map()、.filter()、.take()、.drop() 等辅助方法,并采用惰性求值。与数组方法的链式调用相比,这种方式可以减少中间数组的创建,并在满足终止条件后停止读取后续元素。
javascript
const result = array
.map(x => x * 2) // 在内存中创建一个新数组
.filter(x => x > 10) // ......又创建一个
.slice(0, 3); // ......再创建一个
上述数组方法会依次创建中间结果。对于较大的数组或计算成本较高的操作,这可能增加内存占用和计算量。使用迭代器辅助方法可以改写为:
javascript
const result = Iterator.from(array)
.map(x => x * 2)
.filter(x => x > 10)
.take(3)
.toArray(); // 不创建中间数组;收集到 3 个过滤后的结果即停止,最后生成结果数组
.take(3) 获取的是映射和过滤之后的前 3 个结果,不一定只读取 3 个原始输入元素。.toArray() 会创建最终结果数组,前面的迭代器链不需要逐步创建中间数组。
Iterator.from() 适用于数组、Set、Map、生成器等可迭代对象,使这些数据结构能够使用同一套迭代器辅助方法。
Set 集合方法
Set 是保存唯一值的集合。新增的集合方法支持交集、并集、差集、对称差集,以及子集、超集和不相交关系判断。
javascript
const youKnow = new Set(["JS", "Python", "CSS", "SQL"]);
const jobNeeds = new Set(["JS", "TypeScript", "Python"]);
// 岗位要求中,你已经掌握的技能
youKnow.intersection(jobNeeds); // → Set {"JS", "Python"}
// 合并全部技能:你掌握的技能 + 岗位要求
youKnow.union(jobNeeds); // → Set {"JS", "Python", "CSS", "SQL", "TypeScript"}
// 岗位要求中,你还没有掌握的技能,也就是技能缺口
jobNeeds.difference(youKnow); // → Set {"TypeScript"}
// 你掌握了,但岗位不要求的技能
youKnow.difference(jobNeeds); // → Set {"CSS", "SQL"}
// 只出现在其中一个集合中,而非两个集合共有的技能
youKnow.symmetricDifference(jobNeeds); // → Set {"CSS", "SQL", "TypeScript"}
// 岗位要求是否全部包含在你掌握的技能中?
jobNeeds.isSubsetOf(youKnow); // → false
// 你是否掌握了全部岗位要求,甚至更多?
youKnow.isSupersetOf(jobNeeds); // → false
// 你掌握的技能与岗位要求是否完全没有交集?
youKnow.isDisjointFrom(jobNeeds); // → false
正则表达式更新
页面内搜索如果将用户输入直接作为正则表达式,输入中的特殊字符可能改变匹配含义。例如,$ 表示结尾断言,不能直接当作普通字符使用。搜索输入应按正则表达式的规则进行转义。
RegExp.escape() 可以将字符串转换为适合在正则表达式中按字面含义匹配的形式。
javascript
const query = userInput; // 例如 "$5.00 (off!)"
// ❌ 以前:遇到正则表达式特殊字符,搜索就可能出问题
const badRe = new RegExp(query, "g");
// ✅ ES2025:一个方法就解决了
const goodRe = new RegExp(RegExp.escape(query), "g");
正则表达式的 i 标志表示不区分大小写。ES2025 的内联修饰符允许在分组中启用或关闭特定标志,从而在同一个表达式中组合区分大小写与不区分大小写的匹配。
javascript
// 以前:无法混合区分大小写与不区分大小写的匹配
/[a-z]+@[A-Z]+/i // i 标志作用于整个表达式
// ES2025:为各个分组设置内联修饰符
/(?i:[a-z.]+)@(?-i:[A-Z]+)\.(?i:com|org)/
// 第一个分组不区分大小写
// 第二个分组区分大小写
// 第三个分组不区分大小写
Promise 更新
Promise.try() 可以把函数调用纳入 Promise 错误处理流程。同步抛出的错误和异步拒绝都可以通过后续的 .catch() 处理。
javascript
// 一个可能执行异步操作,也可能同步抛出错误的函数
function loadUser(id) {
if (!id) throw new Error("No ID"); // 同步抛出错误
return fetch(`/api/users/${id}`); // 异步操作
}
// ❌ 以前:两条独立的错误处理路径
let p;
try {
p = loadUser(id); // 在这里捕获同步错误......
} catch (e) { handleError(e); }
p?.catch(e => handleError(e)); // ......在这里捕获异步拒绝
// ✅ ES2025:一次调用,一个 .catch()
Promise.try(() => loadUser(id))
.then(response => response.json()) // 将 Response 解析为用户数据
.then(user => render(user))
.catch(err => showError(err)); // 两种错误都能捕获
导入属性
导入属性可以声明模块导入的类型。例如,可以将 JSON 文件直接作为模块导入:
javascript
import data from "./file.json" with { type: 'json' }
with 及其后面的对象构成导入属性,用于声明导入类型等信息。
Jake Archibald 在《Importing vs fetching JSON》(导入 JSON 与通过 fetch 获取 JSON)中分析了 JSON 模块导入的限制:静态导入失败会影响整个模块依赖图。动态 import() 可以捕获导入失败:
javascript
try {
const { default: data } = await import(url, {
with: { type: 'json' },
});
} catch (error) {
// 回退逻辑
}
动态导入与 fetch 在响应信息和数据生命周期方面存在差异。模块导入不会像 fetch 那样返回响应对象;导入的数据会保留在页面的模块依赖图中,而通过 fetch 获取的数据在不再被引用后可以被垃圾回收。
导入属性也可用于 CSS 模块脚本:
javascript
import componentStyles from "./component.css" with { type: "css" };
CSS 模块脚本允许将样式保存在独立的 CSS 文件中,并通过 adoptedStyleSheets 应用于 Web Component 的 Shadow DOM。CSS 文件可以与对应的 JavaScript 组件放在同一目录中。
javascript
import sheet from './styles.css' with { type: 'css' };
class MyComponent extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.adoptedStyleSheets = [sheet];
}
// 其他组件逻辑......
}
ES2025 还包含本文未展开的特性,例如 Float16Array,可在适合的场景中以较低的数值精度换取更低的内存占用。
ECMAScript 2026(预计于 2026 年中发布)
原文发布时,ECMAScript 2026 预计在年中发布。以下是原文列出的已进入 Stage 4、预计纳入该版本的特性。
Temporal API
Temporal API 提供日期、时间、时区和时间跨度的处理能力。此前,相关需求常借助 Moment 等第三方库实现。
原文发布时,Safari 的 Temporal 支持正在开发,并已进入技术预览版。
Temporal 可以直接获取指定时区的当前时间:
javascript
const now = Temporal.Now.zonedDateTimeISO("America/New_York");
// 适合程序处理的日期时间表示
console.log(now.toString());
// 或者更便于阅读的表示......
console.log(now.toLocaleString());
TC39 会议资料中的以下示例,可将会议时间转换为当前系统时区的时间:
javascript
Temporal.ZonedDateTime.from('2026-03-10T10:00[America/New_York]')
.withTimeZone(Temporal.Now.timeZoneId()) // 你所在的时区
.toLocaleString();
Temporal 还提供日历日期运算和时间跨度比较。
使用 Date.setMonth() 为 1 月 31 日增加一个月时,超出 2 月范围的日期会溢出到 3 月:
javascript
const date = new Date(2026, 0, 31); // 1 月 31 日
date.setMonth(date.getMonth() + 1); // "加上一个月"
console.log(date.toDateString()); // Tue Mar 03 2026(日期溢出到 3 月)
使用 Temporal 可以得到以下结果:
javascript
const jan31 = Temporal.PlainDate.from("2026-01-31");
const feb = jan31.add({ months: 1 });
console.log(feb.toString()); // 2026-02-28 ✅
时间跨度可以通过 Temporal.Duration.compare() 比较:
javascript
const a = Temporal.Duration.from({ hours: 25 });
const b = Temporal.Duration.from({ days: 1 });
const cmp = Temporal.Duration.compare(a, b, { relativeTo: Temporal.Now.plainDateISO() });
console.log(cmp); // 1(25 小时 > 1 天)✅
显式资源管理
显式资源管理通过 using 和 await using 声明需要释放的资源。using 用于同步释放,离开作用域时调用 [Symbol.dispose]();await using 用于异步释放,离开作用域时调用并等待 [Symbol.asyncDispose]()。
javascript
class FileHandle {
constructor(path) {
this.path = path;
console.log(`Opened ${path}`);
}
async write(data) {
// ......写入数据
}
async [Symbol.asyncDispose]() {
await someFlushOperation();
console.log(`Flushed and closed ${this.path}`);
}
}
async function saveData() {
await using file = new FileHandle("output.txt");
await file.write("hello world");
// 文件会在这里自动刷新并关闭,即使抛出了错误
}
DisposableStack 和 AsyncDisposableStack 可以登记多个资源或清理回调,在释放时按登记的相反顺序执行清理。
javascript
async function runJob() {
await using stack = new AsyncDisposableStack();
const db = stack.use(await openDatabase());
const file = stack.use(new FileHandle("output.txt"));
const tmpDir = stack.defer(async () => removeTempDir("/tmp/job"));
// 执行工作......
await file.write(await db.query("SELECT * FROM jobs"));
// 这三项清理会在这里按相反顺序执行,即使过程中抛出了错误
}
Array.fromAsync 与迭代器串接
原文提到,Array.fromAsync 在 2024 年已有实现,并预计纳入 ES2026。它会遍历异步迭代器,等待每个产出的值,并将结果收集为一个普通数组。
javascript
async function* fetchNumbers() {
yield 1;
await new Promise(r => setTimeout(r, 100)); // 模拟异步延迟
yield 2;
await new Promise(r => setTimeout(r, 100));
yield 3;
}
const numbers = await Array.fromAsync(fetchNumbers());
console.log(numbers); // [1, 2, 3]
Array.fromAsync 可用于收集分页等异步操作产生的结果,也可以接收 Promise 数组并等待各个值。
Iterator.concat 可以按顺序串接多个迭代器,以惰性方式遍历其中的元素,而不必先将它们展开到一个数组中。提前退出迭代时,不需要继续收集后续元素。
javascript
const page1 = [{ id: 1 }, { id: 2 }][Symbol.iterator]();
const page2 = [{ id: 3 }, { id: 4 }][Symbol.iterator]();
const page3 = [{ id: 5 }, { id: 6 }][Symbol.iterator]();
for (const item of Iterator.concat(page1, page2, page3)) {
process(item); // 以惰性方式逐项遍历所有分页
}
Error.isError()
Error.isError() 用于判断一个值是否为真正的 Error 对象。
集中式错误上报可能接收来自 Web Worker 或 iframe 等不同执行域(realm)的错误对象,需要能够跨执行域判断错误类型。
Math.sumPrecise
浮点数运算 0.1 + 0.2 的结果为 0.30000000000000004。
原文的示例中,Math.sumPrecise([0.1, 0.2]) 得到相同结果。因此,这个方法并不会把该表达式的结果变为精确的十进制 0.3。
Base64 与十六进制编码
字节数组可以通过 .toBase64() 和 .toHex() 转换为 Base64 或十六进制字符串。
javascript
const val = "Frontend Masters!";
const textEnc = new TextEncoder();
const bytes = textEnc.encode(val);
console.log(bytes.toBase64());
// 'RnJvbnRlbmQgTWFzdGVycyE='
console.log(bytes.toHex());
// '46726f6e74656e64204d61737465727321'
框架有哪些新变化
React 生态
React 19 于 2024 年 12 月发布,原文发布时的版本为 19.2。
原文介绍了 React 生态中的 RSC(React Server Components,React 服务端组件)、React Compiler 和 Server Actions:
- RSC:服务端组件在服务器上执行,相应的组件实现不需要作为客户端 JavaScript 发送,客户端接收渲染所需的数据。
- Server Actions:用于调用在服务器上执行的函数,表单处理是其中一种应用场景。
- React Compiler:通过编译阶段的分析完成部分性能优化,减少手动编写记忆化优化的需求。
原文发布时,React Native 已发布 0.83。
原文指出,2025 年 React 服务端相关技术曾接连出现严重安全漏洞。
Vue 生态
原文发布时,Vue 3.5 为稳定版本,Vue 3.6 已进入 alpha 阶段,包含可选择启用的 Vapor Mode。原文引用的性能目标是与 Solid 和 Svelte 5 相当。
尤雨溪创立的 VoidZero 推出 Vite+,覆盖 Vite、格式化、代码检查和测试等工具。
这些工具并非 Vue 专属,也可用于其他 JavaScript 项目。
原文发布时,Nuxt 已发布 4.0。Nuxt 的维护团队 NuxtLabs 被 Vercel 收购。
Vue 状态管理库 Pinia 已发布 v3,并移除了 Vue 2 支持。
Svelte 生态
Svelte 5 引入 Runes API,调整了响应式机制,采用更细粒度的响应式处理。
JavaScript 运行时
JavaScript 运行时包括浏览器内置运行时,以及可以独立运行程序的 Node.js、Bun、Deno 等。
Node.js、Bun 和 Deno 都增加了 TypeScript 运行支持;Bun 和 Deno 也持续扩展与 Node.js 生态的兼容能力。
Node.js
Node.js 可以直接运行支持的 TypeScript 文件,例如:
bash
node my-script.ts
从 Node 22.18.0 开始,运行支持的 TypeScript 文件不再需要 --experimental-strip-types 标志。这种运行方式会移除类型信息,不执行 TypeScript 类型检查。
Node.js 的更新还涉及安全性、性能和浏览器 JavaScript API 的兼容。
Node.js 提供内置测试运行器,可以减少对外部测试工具的依赖;权限模型用于限制程序对系统资源的访问。
Bun
Bun 1.3 增加了开发服务器相关功能。将 bun 指向 HTML 文件,可以启动开发服务器:
bash
bun './**/*.html'
该方式包含文件处理和打包能力。
Anthropic 于 2025 年年底收购 Bun。
Deno
Deno 2 支持 Node.js 兼容能力,也可以通过 npm: 指定方式使用 npm 包。
Deno 默认限制程序访问敏感资源。其官方文档说明:
Deno 默认是安全的。除非你明确启用权限,否则通过 Deno 运行的程序无法访问敏感 API,例如文件系统、网络连接或环境信息。你必须通过命令行标志或运行时权限提示,明确授予对这些资源的访问权限。这是它与 Node 的一个主要区别:在 Node 中,依赖会自动获得对所有系统 I/O 的完整访问权限,可能给项目引入隐藏的安全漏洞。
构建工具
Vite
Vite 起源于 Vue 生态,也支持其他前端框架和项目。
Vite 本地开发时按需处理发生变化的模块,生产构建时执行打包。
原文发布时,Vite 已发布 v8,打包工具由 Rollup 切换为 Rolldown。统一工具链可以在不同工具之间共享解析器等基础能力。
Vite+ 覆盖开发服务器、格式化、代码检查、类型检查、测试、任务运行、monorepo 支持和打包。
VoidZero 还在开发名为 Void 的部署平台,使用 Cloudflare 的托管、数据存储和云函数等服务。
原文列出的平台能力包括数据库、KV 存储、对象存储、AI 推理、身份验证、队列和定时任务。
Astro、SolidStart、SvelteKit 和 Nuxt 等框架使用 Vite。Next.js 使用 webpack 和 Turbopack。原文还提到 Cloudflare 借助 AI 进行的 Next.js 到 Vite 的移植项目。
Turbopack
Turbopack 是 Vercel 开发、基于 Rust 的打包工具,从 Next.js v16 开始成为默认打包工具。原文引用的刷新速度提升为此前 webpack 方案的 5~10 倍;具体表现取决于项目和测试场景。
webpack
webpack 的 2026 年开发计划包含减少对部分 loader 的依赖,以及简化配置等改进。
TypeScript
原文发布时,TypeScript 已发布 v6,v7 预计于 2026 年中推出,并计划切换到基于 Go 的编译器。v6 包含为编译器迁移做准备的配置调整。Bytes 简报将这些调整概括为:
严格模式现在默认是
true,module默认是esnext,target会跟随当年的 ES 规范,目前为es2025。types现在默认是一个空数组,不再自动把node_modules/@types中的所有类型都纳入。仅最后这一项,就可能导致很多项目无法正常工作,不过也应该能让它们提速 20%~50%。
原文提到,新的编译器在 VS Code 和 Playwright 等使用场景中展示了约 10 倍的速度提升;这属于原文引用的性能结果。
原文引用的 GitHub 统计显示,TypeScript 在相关语言统计中排名第一,同比增长 66%。
在 JavaScript 中直接加入类型?
JavaScript 类型注释提案探索在语言中直接加入类型语法。原文发布时,该提案尚未提供可替代 TypeScript 完整能力的方案。
AI
AI 编程工具可用于生成 JavaScript 和 TypeScript 代码。生成的代码仍需进行审查、类型检查和测试。
测试
JavaScript 测试工具包括 Jest、Jasmine、Mocha 和 Vitest 等。
Vitest 兼容 Jest 的测试 API,可以支持现有测试的迁移。
Vitest 的浏览器模式可以在真实浏览器中运行测试,并可配合 Playwright 进行组件测试。Playwright 本身也支持端到端测试。
元框架
Next.js
Next.js v16 是首个默认使用 Turbopack 的版本,同时改进了日志和错误信息。
原文介绍了 Next.js 对 React Compiler 和 React Server Components 的集成。性能收益需要结合具体应用场景评估。
Next.js 提供 MCP 服务器,可以向接入的 AI 开发工具提供项目相关信息。
原文还提到 React 19 相关的 <ViewTransition> 支持。
Remix / React Router
Remix 被 Shopify 收购后发布了 v2。原计划作为 Remix v3 发布的内容,随后整合为 React Router v7。
原文发布时,新的 Remix v3 正在开发,计划使用独立组件模型,不再以 React 为组成部分。
Remix Jam 活动介绍了相关开发方向。
TanStack
TanStack 是一组工具的集合,包含 TanStack Router 等。其路由能力进一步扩展为框架 TanStack Start。
Astro
Cloudflare 于 2026 年收购 Astro。原文还提到基于 Astro 构建的 WordPress 类项目。
Astro 采用默认静态的页面模式,支持现代 JavaScript 框架的组件化架构,并允许按需加入动态行为。
原文介绍了 Astro 6.0 的开发运行时自定义、内容安全策略和实验性编译器等功能,以及随后 6.1 版本的配置改进。
npm
微软/GitHub 于 2020 年收购 npm。
npm 生态在 2025 年发生了多起供应链安全事件。s1ngularity 事件涉及凭证、令牌和配置文件窃取,并将这些信息公开发布到 GitHub。
接着是 debug/chalk 事件:恶意的软件包更新被发布出去,能够把加密货币交易重定向到攻击者的钱包。
Shai-Hulud 蠕虫事件涉及自我复制和凭证窃取。原文称,其 2.0 版本包含覆盖或删除用户主目录文件的行为,事件波及 796 个 npm 包,相关包的下载量超过 2000 万次。
Socket 等工具用于检测依赖包中的风险行为。
工程基础能力
语言基础、异步机制、模块系统和运行时行为,是理解框架与工程工具的基础。
AI 辅助生成代码的开发流程仍包含需求分析、架构设计、代码审查和测试验证。