【翻译】ECMAScript 2026 有哪些新特性

0 阅读5分钟

ECMAScript 2026 有哪些新特性

原文链接:pawelgrzybek.com/whats-new-i… 原文作者:Paweł Grzybek

文章头图

发布:2026.07.06 · 约 6 分钟阅读

我如今写 Go 比写 JavaScript 更多,并不是因为语言本身有什么问题,而是因为我厌倦了这套生态。语言本身其实很好——它是我第一门真正能高效产出的编程语言,而我至今仍有动力跟上其规范中的新特性。每年语言规范最终获批后,我都会写一篇介绍当年新增内容的文章。如果你好奇过去十年这门语言如何演变,下面是完整列表。

2026 年 6 月 30 日,Ecma International 批准了 ECMAScript® 2026 语言规范,因此这里汇总一下新增内容。

Array.fromAsync

要把同步迭代器转换成数组,我们可以用 for 循环,或者用 Array.from()。这一能力从 2015 年起就已存在于语言中。

function* gen(start, end) {
  for (let i = start; i <= end; i++) {
    yield i;
  }
}

const arrOne = [];
for (const n of gen(2024, 2026)) {
  arrOne.push(`ECMAScript ${n}`);
}
console.log("arrOne", arrOne);
// 输出:arrOne [ 'ECMAScript 2024', 'ECMAScript 2025', 'ECMAScript 2026' ]

const arrTwo = Array.from(gen(2024, 2026), (n) => `ECMAScript ${n}`);
console.log("arrTwo", arrTwo);
// 输出:arrTwo [ 'ECMAScript 2024', 'ECMAScript 2025', 'ECMAScript 2026' ]

过去几年里,对异步迭代器我们只有 for await 循环,而没有 Array 原型上的静态方法辅助。

async function* gen(start, end) {
  for (let i = start; i <= end; i++) {
    yield i;
  }
}

const arr = [];
for await (const n of gen(2024, 2026)) {
  arr.push(`ECMAScript ${n}`);
}
console.log("arr", arr);
// 输出:arr [ 'ECMAScript 2024', 'ECMAScript 2025', 'ECMAScript 2026' ]

今年,Array.fromAsync 加入规范,填补了这一空白。它可以从异步迭代器,或从产出 Promise 的同步生成器,生成数组。感谢 J.S. Choi 的 Array.fromAsync for JavaScript 提案

async function* asyncGen(start, end) {
  for (let i = start; i <= end; i++) {
    yield i;
  }
}

const arrOne = [];
for await (const n of asyncGen(2024, 2026)) {
  arrOne.push(`ECMAScript ${n}`);
}
console.log("arrOne", arrOne);
// 输出:arrOne [ 'ECMAScript 2024', 'ECMAScript 2025', 'ECMAScript 2026' ]

const arrTwo = await Array.fromAsync(
  asyncGen(2024, 2026),
  (n) => `ECMAScript ${n}`,
);
console.log("arrTwo", arrTwo);
// 输出:arrTwo [ 'ECMAScript 2024', 'ECMAScript 2025', 'ECMAScript 2026' ]

function* gen(start, end) {
  for (let i = start; i <= end; i++) {
    yield Promise.resolve(i);
  }
}

const arr = await Array.fromAsync(gen(2024, 2026), (n) => `ECMAScript ${n}`);
console.log("arr", arr);
// 输出:arr [ 'ECMAScript 2024', 'ECMAScript 2025', 'ECMAScript 2026' ]

Error.isError

感谢 Jordan Harband 的 Error.isError() 提案。这是比 instanceof Error 安全得多的替代方案。

try {
  throw new Error("This is an error");
} catch (error) {
  console.log(Error.isError(error));
  // 结果:true
}

try {
  throw "This is not an error";
} catch (error) {
  console.log(Error.isError(error));
  // 结果:false
}

Math.sumPrecise

把一串数字加起来,是 Array.reduce() 非常常见的用法。感谢 Kevin Gibbons 提交了 Math.sumPrecise 函数提案,我们不再需要 reducer,而且作为额外收益,还能为浮点数获得更好的精度。

const values = [1e20, 0.1, -1e20];

const sumOne = values.reduce((a, b) => a + b, 0);
console.log("sumOne", sumOne);
// 输出:sumOne 0

const sumTwo = Math.sumPrecise(values);
console.log("sumTwo", sumTwo);
// 输出:sumTwo 0.1

Uint8Array 与 Base64 及十六进制互转

一套把 Uint8Array 转成 base64 与十六进制值、再转回来的内置机制。这是语言中又一项能去掉额外依赖的新增能力。

const value = new Uint8Array([69, 83, 50, 48, 50, 54]);

const valueBase64 = value.toBase64();
const valueHex = value.toHex();

console.log({ value });
console.log({ valueBase64 });
// 输出:{ value: Uint8Array(6) [ 69, 83, 50, 48, 50, 54 ] }
// 输出:{ valueBase64: 'RVMyMDI2' }

const valueBase64Decoded = Uint8Array.fromBase64(valueBase64);
const valueHexDecoded = Uint8Array.fromHex(valueHex);

console.log({ valueBase64Decoded });
console.log({ valueHexDecoded });
// 输出:{ valueBase64Decoded: Uint8Array(6) [ 69, 83, 50, 48, 50, 54 ] }
// 输出:{ valueHexDecoded: Uint8Array(6) [ 69, 83, 50, 48, 50, 54 ] }

// 玩梗 😜
console.log(new TextDecoder().decode(valueBase64Decoded));
// 解码结果:ES2026

迭代器串接(Iterator Sequencing)

在 ES2026 之前,组合多个迭代器意味着写一个生成器,再按顺序对各个迭代器做 yield。看这个例子。

const iOne = Iterator.from([2022, 2023]);
const iTwo = Iterator.from([2025, 2026]);

function* combine(...iterators) {
  for (const source of iterators) {
    yield* source;
  }
}

const combinedIterator = combine(iOne, iTwo);
console.log("combinedIterator", Array.from(combinedIterator));
// 输出:combinedIterator [ 2022, 2023, 2025, 2026 ]

感谢 Michael Ficarra,我们不必再折腾这一套,因为迭代器串接(Iterator Sequencing)提案已经落地。额外惊喜是,它还提供了方便地插入中间值的方式(看下面例子里的 2024)。

const iOne = Iterator.from([2022, 2023]);
const iTwo = Iterator.from([2025, 2026]);

const combinedIterator = Iterator.concat(iOne, [2024], iTwo);
console.log("combinedIterator", Array.from(combinedIterator));
// 输出:combinedIterator [ 2022, 2023, 2024, 2025, 2026 ]

JSON.parse 源文本访问

在 JavaScript 里,JSON 的编组/解组是有损的。看下面两个例子:反序列化得到了不正确的值;而对一个完全合法的 BigInt 做序列化则会抛出 TypeError

console.log(JSON.parse("999999999999999999"));
// 输出(精度丢失):1000000000000000000

console.log(JSON.stringify(9999999999999999n));
// 抛出:TypeError: Do not know how to serialize a BigInt

Richard Gibson 的 JSON.parse 源文本访问提案为这两个问题都提供了解决方案。JSON.parse 的 reviver 回调除了大家熟悉的 keyvalue 之外,现在还暴露第三个参数,让我们能访问原始的 source 值。类似地,在 JSON.stringify 的 replacer 函数中,可以通过新的 JSON.rawJSON() 把值原样传出。

console.log(
  JSON.parse("999999999999999999", (key, value, { source }) => BigInt(source)),
);
// 输出:999999999999999999n

console.log(
  JSON.stringify(9999999999999999n, (key, value) => JSON.rawJSON(value)),
);
// 输出:9999999999999999

Upsert

这一项纯粹是便利性改进,会大幅减少样板代码。不必再显式检查 Map/WeakMap 上是否已有该键再插入,因为现在可以用 getOrInsert 方法完成插入或取得(upsert)。得益于 Daniel Minor、Lauritz Thoresen Angeltveit、Jonas Haukenes、Sune Lianes、Vetle Larsen 与 Mathias Hop Ness 的辛勤工作,Upsert 提案成为了语言的一部分。谢谢你们!

const settings = new Map();
settings.set("language", "en");

// 再也不用折腾这一套了 🎉
// 旧写法:if (!settings.has("theme")) {
// 旧写法:  settings.set("theme", "dark");
// 旧写法:}
// 旧写法:if (!settings.has("language")) {
// 旧写法:  settings.set("language", "pl");
// 旧写法:}

console.log(settings.getOrInsert("theme", "dark"));
// 输出:dark
console.log(settings.getOrInsert("language", "pl"));
// 输出:en

喜欢这篇文章吗?请分享给你的朋友们。谢谢!