ES2022-2025实用特性盘点:让JavaScript代码更短更安全

摘要:日常写JS时,往往用顺手的那几招就够用了。但ECMAScript每年都会加一批小改进——不搞大新闻,却能让代码更短、更安全、更好读。这篇文章整理了一批近几年落地的实用特性,并配上代码示例,方便你在真实项目里直接用起来。

日常写JS时,往往用顺手的那几招就够用了。但ECMAScript每年都会加一批小改进——不搞大新闻,却能让代码更短、更安全、更好读。这篇文章整理了一批近几年落地的实用特性,并配上代码示例,方便你在真实项目里直接用起来。


一、ES2022:少写废话,多写意图

1. 顶层await

解决啥问题:以前在模块顶层想「先拉配置再往下跑」,只能包一层async函数再调用,多一层壳。

以前常见写法:

async function bootstrap() {
  const settings = await loadSettings();
  runApp(settings);
}
bootstrap();

现在可以这样:

const settings = await loadSettings();
runApp(settings);

模块顶层直接await,启动逻辑更直观,少一点仪式感。

2. 私有字段(#)

解决啥问题:JS一直没真正的类私有字段,大家用_private之类的约定,本质上还是「君子协定」。

现在可以这样:

class TaskQueue {
  #pending = [];

  enqueue(task) {
    this.#pending.push(task);
  }

  get size() {
    return this.#pending.length;
  }
}

在类外访问instance.#pending会直接报错,封装更实在。

3. Error的cause

解决啥问题:一层层往上抛错时,原始错误容易丢,排查时要猜「到底是哪一环出的问题」。

现在可以这样:

try {
  await fetchFromAPI();
} catch (e) {
  throw new Error('请求失败', { cause: e });
}

日志和DevTools里能看到完整错误链,定位更快。

4. Object.hasOwn()

解决啥问题:判断「是不是自有属性」以前要写Object.prototype.hasOwnProperty.call(obj, 'key'),又长又容易在边缘情况踩坑。

以前:

if (Object.prototype.hasOwnProperty.call(config, 'env')) {
  useEnv(config.env);
}

现在:

if (Object.hasOwn(config, 'env')) {
  useEnv(config.env);
}

语义清晰,写法也干净。

5. 数组的.at()(负索引)

解决啥问题:取「最后一个元素」总要写arr[arr.length - 1],取倒数第二个更啰嗦。

以前:

const last = items[items.length - 1];
const secondLast = items[items.length - 2];

现在:

const last = items.at(-1);
const secondLast = items.at(-2);

负索引从末尾算起,可读性更好。


二、ES2023:少改原数据,多返回新数据

这一批的主线是:尽量不原地修改,避免共享引用带来的隐蔽bug。

6. toSorted() / toReversed() / toSpliced()

解决啥问题:sort()、reverse()、splice()都会改原数组,一不留神就影响到别处。很多人只好先[...arr].sort(),语义不直接。

以前常见写法:

const byDate = [...list].sort((a, b) => a.date - b.date);
const reversed = [...list].reverse();

现在:

const byDate = list.toSorted((a, b) => a.date - b.date);
const reversed = list.toReversed();
const withoutSecond = list.toSpliced(1, 1); // 去掉下标1的一个元素,返回新数组

原数组不变,返回新数组,更适合状态管理和函数式写法。

7. findLast() / findLastIndex()

解决啥问题:只有find时,要找「最后一个满足条件的」要么自己倒着循环,要么[...arr].reverse().find(fn),既啰嗦又可能误用reverse。

以前:

const lastEven = [...nums].reverse().find((n) => n % 2 === 0);

现在:

const lastEven = nums.findLast((n) => n % 2 === 0);
const lastEvenIndex = nums.findLastIndex((n) => n % 2 === 0);

意图一眼能看懂。


三、ES2024:数据分组与异步控制

8. Object.groupBy()

解决啥问题:按某个键把数组分组,以前要么手写reduce,要么自己封一个groupBy工具函数。

以前:

const byStatus = list.reduce((acc, item) => {
  const k = item.status;
  if (!acc[k]) acc[k] = [];
  acc[k].push(item);
  return acc;
}, {});

现在:

const byStatus = Object.groupBy(list, (item) => item.status);

注意:键会变成字符串。若要用对象/其他类型当键,用Map.groupBy()。

9. Promise.withResolvers()

解决啥问题:需要「先拿到resolve/reject,稍后再调用」时,以前要在外层声明变量再塞进new Promise,写法别扭。

以前:

let resolve;
const done = new Promise((r) => { resolve = r; });
// 某事件触发后:resolve(value);

现在:

const { promise, resolve, reject } = Promise.withResolvers();
// 某事件触发后:resolve(value);

事件、队列、超时等场景里会清爽很多。

10. 可调整大小的ArrayBuffer

解决啥问题:以前ArrayBuffer长度固定,流式或动态长度时要自己管理多段buffer。

现在可以指定最大长度:

const buffer = new ArrayBuffer(256, { maxByteLength: 4096 });
// 后续可通过.resize()在256~4096之间调整

适合流式、Worker、二进制协议等场景。


四、ES2025:迭代器、集合与安全正则

11. 迭代器上的map / filter / take

解决啥问题:数组链式map → filter → slice会每一步都生成新数组,数据量大时浪费内存。迭代器可以「按需计算」,不先占满内存。

以前(每一步都是新数组):

const result = data.map((x) => x.value).filter((v) => v > 0).slice(0, 10);

现在(懒求值,用迭代器):

const result = data.values()
  .map((x) => x.value)
  .filter((v) => v > 0)
  .take(10)
  .toArray();

适合大列表、流式数据或无限序列。注意:这里的map/filter/take是迭代器上的方法,和Array.prototype上的不同,需要先拿迭代器(如arr.values())。

12. Set的intersection / union / difference

解决啥问题:求交集、并集、差集以前要自己用filter、has拼,或者转成数组再算。

以前:

const both = new Set([...a].filter((x) => b.has(x)));
const all = new Set([...a, ...b]);

现在:

const both = a.intersection(b);
const all = a.union(b);
const onlyInA = a.difference(b);

集合运算更贴近数学表达,可读性更好。

13. RegExp.escape()

解决啥问题:用用户输入拼正则时,输入里若有( ) [ ] * + ?等会变成正则语法,既容易报错也有安全风险。以前要自己写一长串replace转义。

现在:

const safe = RegExp.escape(userInput);
const re = new RegExp(safe, 'i');

动态正则更安全,也省得自己维护转义规则。

14. Promise.try()

解决啥问题:有的函数可能同步抛错、也可能返回Promise,想统一用await包一层时,要自己区分sync/async。Promise.try把「可能抛错的同步/异步逻辑」统一成Promise。

示例:

const result = await Promise.try(() => maybeSyncOrAsync());

这样无论函数是同步抛错还是返回Promise,都能用同一套错误处理。

15. Float16Array

解决啥问题:以前只有Float32/Float64,在WebGPU、部分ML或紧凑二进制格式里需要16位浮点。

现在:

const data = new Float16Array(1024);

更省内存,也方便和GPU/跨语言二进制格式对接。


五、整体在往哪走

拉远一点看,这几年JS的更新有一个共同方向:

  • 少改原数据:toSorted、toReversed、toSpliced、findLast等,都在鼓励「返回新的」而不是「改原来的」

  • 意图更明确:hasOwn、at(-1)、groupBy、Set的intersection/union/difference,名字即语义

  • 异步更稳:顶层await、Error.cause、Promise.withResolvers、Promise.try,让异步和错误链更清晰

  • 更安全、更省资源:私有字段、RegExp.escape、迭代器懒求值、Float16Array,分别在封装、安全、性能和内存上补了一块

不必追求「全背下来」——用到时查一下MDN或Node文档即可。更值得做的是:知道有这些能力存在,下次写reduce分组、取最后一个元素、或「不改变原数组的排序」时,能想到用新API,代码会更好维护。

如果你有固定用的Node版本,可以先在Node兼容表或caniuse上看下目标环境是否支持;部分ES2025特性需要较新的运行时(如Node 22+)。按需选用,就能把这些「悄悄好用」的特性真正用起来。

本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://shenqiku.cn/article/FLY_13556