ES2026,早该这样了

0 阅读5分钟

"Perfection is achieved not when there is nothing more to add, but when there is nothing left to take away." — Antoine de Saint-Exupéry

6月30号,ECMAScript 2026 的规范悄悄过审了。

没有发布会,没有倒计时,连个热搜都没上。说实话,挺符合这次更新的气质——闷声干活,不整花里胡哨的。

打开 changelog 扫了一遍,我的第一反应不是"哇塞",而是——

"这玩意儿怎么现在才有?"


Map.getOrInsert:终于不用写三行废话了

写 JS 的应该都经历过这种场景:往 Map 里塞数据,key 存在就追加,不存在就初始化。

ES2026 之前,你的代码大概长这样:

const map = new Map();

function addTag(userId, tag) {
  if (!map.has(userId)) {
    map.set(userId, []);
  }
  map.get(userId).push(tag);
}

三行代码做一件事:检查、初始化、追加has 一次查找,get 又一次查找,加上 set 的那一次——恭喜你,为了一个简单的"有就拿、没有就建",你让引擎跑了三趟。

ES2026 之后:

const map = new Map();

function addTag(userId, tag) {
  map.getOrInsert(userId, []).push(tag);
}

一行。结束。

而且还有个 getOrInsertComputed ,只在 key 不存在时才执行回调去算默认值。这对那些初始化成本高的场景来说,省的不只是代码行数,还有真金白银的运行时开销。

// 连接池场景:不存在才创建,存在直接复用
const pool = new Map();
const conn = pool.getOrInsertComputed(dbHost, (host) => {
  return createConnection(host); // 只在首次才会执行
});

说白了,这个 API 就是把 Python 的 dict.setdefault() 和 Ruby 的 Hash#fetch 那套搬过来了。人家十几年前就有的东西,JS 到 2026 年才给你。

"不是JS用不起,而是TC39推得慢。"


Error.isErrorinstanceof 的坑,终于有人填了

你有没有遇到过这种灵异事件:一个东西明明是 Error,instanceof Error 却告诉你"不是"?

别怀疑自己。这是 JS 的经典设计遗产——跨 Realm 的原型链断裂。iframe 里抛出的 Error、Worker 里传递的 Error、甚至 vm.runInNewContext 里创建的 Error,它们的 Error 构造函数和你当前环境的 Error 压根不是同一个对象。

// iframe 里的代码抛了个错
const err = iframeWindow.someFunction(); 

console.log(err instanceof Error); // false
// 你盯着屏幕:???它明明就是个 Error 啊???

以前的应对方案通常是这样:

// "鸭子类型"检测法,能跑但心里没底
function isError(value) {
  return value && typeof value.message === 'string' 
    && typeof value.stack === 'string';
}

能用,但你心里清楚这就是在糊弄。任何带 messagestack 属性的对象都能骗过去。

ES2026 给了一个干净利落的解法:

// 跨 iframe、跨 Worker、跨 Realm,全部搞定
Error.isError(err); // true,不管你从哪来的

没有 polyfill 的弯弯绕绕,没有鸭子类型的心虚。原生方法,标准行为,一锤定音。

类似的设计其实早就有先例了——Array.isArray() 干的就是同样的事。只是 Error 这边拖了十几年才补上,算是还了一笔技术债。


Iterator.zip:拉链合并,不用再手搓索引了

同时遍历两个数组,按索引一一对应?以前你大概率是这么写的:

const names = ['Alice', 'Bob', 'Charlie'];
const scores = [92, 87, 95];

// 经典 for 循环手动对齐
const results = [];
for (let i = 0; i < Math.min(names.length, scores.length); i++) {
  results.push([names[i], scores[i]]);
}

手搓索引,Math.min 防越界,每次写都觉得自己在重复劳动。Python 程序员看到这段代码大概会笑出声——人家一个 zip() 就搞定的事。

ES2026 终于把这个轮子给你造好了:

const names = ['Alice', 'Bob', 'Charlie'];
const scores = [92, 87, 95];

const results = Iterator.zip([names, scores]).toArray();
// [[Alice, 92], [Bob, 87], [Charlie, 95]]

而且给了三种模式,比 Python 的还周到:

// shortest(默认):以最短的为准
Iterator.zip([a, b]);

// longest:以最长的为准,短的用 undefined 填
Iterator.zip([a, b], { mode: 'longest' });

// strict:长度不一致直接报错,适合数据必须对齐的场景
Iterator.zip([a, b], { mode: 'strict' });

strict 模式是我最喜欢的。数据对不齐就直接炸给你看,别让那些 undefined 悄悄溜进你的业务逻辑里——等你发现的时候,线上已经出了两个工单了。


Math.sumPrecise:浮点数的坑,官方来填了

经典面试题:0.1 + 0.2 等于多少?

console.log(0.1 + 0.2); // 0.30000000000000004

这个坑每个 JS 开发者都踩过,但你有没有想过,当你需要对一个大数组做求和的时候,这些微小的精度误差会像滚雪球一样越积越大?

// 传统写法:误差在每次累加时都在积累
const total = values.reduce((sum, v) => sum + v, 0);

ES2026 的 Math.sumPrecise 用了更聪明的算法来做浮点求和——在运算过程中尽可能保持精度,最后才做一次舍入:

const values = [0.1, 0.2, 0.3, 0.4, 0.5];
Math.sumPrecise(values); // 1.5,干净利落

搞金融计算的、做数据统计的、写科学计算的——这个方法是给你们量身定做的。以后别再自己手搓 Kahan 求和算法了,标准库帮你兜底。


说点真心话

ES2026 不是那种让你"哇塞"的大版本。没有新语法糖,没有革命性的范式转移,没有能写进简历里炫耀的关键词。

但这恰恰是我觉得最舒服的一次更新。

每一个新 API 都在解决一个真实的、具体的、你写过一万遍 workaround 的痛点。

Map.getOrInsert 终结了 has-get-set 三件套。Error.isError 填上了 instanceof 的跨域天坑。Iterator.zip 让你不用再手搓索引对齐。Math.sumPrecise 让浮点求和不再是玄学。

这些API不酷,但是好用。就像一个老老实实修bug的同事——不会在周会上抢着发言,但你真心希望组里多来几个这样的人。

TC39 这次干了件正经事。


📊 数据来源与参考资料