【JavaScript】ES2025の新機能全部解説する

[ES2025](https://qiita.com/rana_kualu/items/e82790fa50c05b167dcc) / [ES2024](https://qiita.com/rana_kualu/items/16539958b0c9d3b3dba8) / [ES2023](https://qiita.com/rana_kualu/items/84f66fe970f7feccf367) / [ES2022](https://qiita.com/rana_kualu/items/8bafecd760ae69cfac41)

JavaScriptの仕様は、[TC39](https://tc39.es/)というところで決められています。
ブラウザベンダや関係者が定期的に会合を行い、様々な新機能について話し合って今後のJavaScriptの方向性を決めていきます。

ここでは2024年にFinishedになった、すなわち仕様が確定して複数のブラウザで実装がなされたproposalについて紹介してみます。
主要ブラウザでは既にほとんどの機能を使用可能です。

ちなみに、2024年2月から2025年1月までにFinishedになったproposalがES2025と呼ばれます。
ややこしいですね。

# [Finished Proposals](https://github.com/tc39/proposals/blob/main/finished-proposals.md)

## [Promise.try](https://github.com/tc39/proposal-promise-try)

同期かもしれないし非同期かもしれない、Promiseを返すかもしれないしそうでないかもしれない、そんな関数があります。
その関数を使う際に、いちいち区別して書きたくないから一括でどうにかできない？

```js
function functionAsync(){
  return new Promise((resolve, reject) => {});
}

function functionSync(){
  return 'hoge';
}

functionAsync().then(); // うごく
functionSync().then();  // エラー
```

こうすれば一括で動かすことができます。

```js
Promise.resolve().then(functionAsync);
Promise.resolve().then(functionSync);
```

ただし、これは不必要に非同期で動くので[遅くなるらしい](https://zenn.dev/uhyo/articles/return-await-promise)です。
何言ってるのかよくわからない。

遅延なく実行させたい場合は、こう書かないといけません。

```js
new Promise(resolve => resolve(functionAsync()));
new Promise(resolve => resolve(functionSync()));
```

しかしこれは文法もわかりにくいし、どうしてこんな回りくどい書き方をしているのかも理解しがたいですね。

そこで`Promise.try()`です。

```js
Promise.try(functionAsync);
Promise.try(functionSync);
```

どんな関数も統一的な書式で楽観的に同期的に安全に処理できました。
めでたし。

同じ機能を実現するライブラリ[p-try](https://www.npmjs.com/package/p-try)はなんと[100億回以上](https://npm-stat.com/charts.html?package=p-try&from=2011-01-01)ダウンロードされています。


## [Sync Iterator helpers](https://github.com/tc39/proposal-iterator-helpers)

JavaScriptの[Iterator](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Iterator)まわりについては、[JavaScriptのIterator / Generatorの整理](https://zenn.dev/qnighy/articles/112af47edfda96)に詳しく書かれています。

＿人人人人人人人人人人人人＿
＞　全く意味がわからない　＜
￣Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^￣


まあともかく、なんか適当なIteratorがあったとします。

```js
const iter = [1,2,3,4,5][Symbol.iterator]();

for (value of iter) {
  console.log(value); // 1,2,3,4,5
}
```

これをループを始める前に外から触れるようになります。

```js
// 2倍にする
iter2 = iter.map(value => { return value * 2; });
for (value of iter2) {
  console.log(value); // 2,4,6,8,10
}

// 奇数のみ取得
iter3 = iter.filter(value => { return value % 2; });
for (value of iter3) {
  console.log(value); // 1, 3, 5
}

// LIMIT
iter4 = iter.take(3);
for (value of iter4) {
  console.log(value); // 1, 2, 3
}

// OFFSET
iter5 = iter.drop(3);
for (value of iter5) {
  console.log(value); // 4, 5
}

// 増やす
iter6 = iter.flatMap(value => { return [value, value] });
for (value of iter6) {
  console.log(value); // 1,1,2,2,3,3,4,4,5,5
}

// まとめる
total = iter.reduce((sum, value) => { return sum + value; }); // 15

// 配列にする
arr = iter.toArray(); // [1,2,3,4,5]
```

ということでIteratorを配列のように操作できるようになりました。
注意点として、無限IteratorをreduceやtoArrayしようとすると死にます。
警告とかは出してくれません。

配列に存在するメソッドの中でも、sortやreverseといった並びを変更するメソッドは実装しようがないので対応していません。
またfindやincludesなど間を飛ばすメソッドにも対応していません。
とはいえ、これまではnext()しかなかったことを考えると、だいぶ便利になることでしょう。

このproposalが対応したのは同期イテレータIteratorだけです。
非同期イテレータ[AsyncIterator](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/AsyncIterator)については対応していません。
元々はひとつのproposalだったのですが諸事情で分割されたみたいで、[分割されたほうのproposal](https://github.com/tc39/proposal-async-iterator-helpers)はまだStage2.7です。


## [Import Attributes](https://github.com/tc39/proposal-import-attributes)

外部ファイルを読み込めるようになります。

```js
import styles from "./styles.css" with { type: "css" };
import json from "./foo.json" with { type: "json" };
```

ポイントとしては、読み込む側がデータ型を指定するところです。

元々`import json from "http://example.jp/foo.json";`でJSONを取り込めれば便利じゃんということで、既に[WHATWGにmerge](https://github.com/whatwg/html/pull/4407)されていました。
しかし、[これ相手がJSONのふりしてJavaScriptを返してきたら脆弱性になるだろう](https://www.w3.org/2019/09/17-components-minutes.html#item18)という指摘が入り、[なんか無駄に長い議論](https://github.com/WICG/webcomponents/issues/839)の末に読み込み側が型を指定することに決まりました。

どうしてこの程度のことを誰も最初に突っ込まなかったのかが謎過ぎる。

あとproposal名がModule attributesからImport assertionsになってさらにImport attributesになったり、属性名がwithからassertになってさらにwithに戻ったりと、やたら紆余曲折が激しいproposalでもありました。


## [JSON Modules](https://github.com/tc39/proposal-json-modules)

上記Import AttributesのJSONだけについてのproposalです。

```js
import json from "./foo.json" with { type: "json" };
import("foo.json", { with: { type: "json" } });
```

元々はImport Attributesと一体だったのが分離されたものです。

結局同じ文法で同時にStage4入りしたので、結果的にはひとつのproposalとして扱われたと考えていいかもしれません。


## [New Set methods](https://github.com/tc39/proposal-set-methods)

Setに集合演算メソッドが幾つか増えます。

```js
// AND
new Set([1, 2]).intersection( new Set([1, 3])); // {1}

// OR
new Set([1, 2]).union( new Set([1, 3])); // {1, 2, 3}

// 差集合
new Set([1, 2]).difference( new Set([1, 3])); // {2}

// XOR
new Set([1, 2]).symmetricDifference( new Set([1, 3])); // {2, 3}

// 部分集合
new Set([1]).isSubsetOf( new Set([1, 2])); // true

// isSubsetOfの逆
new Set([1, 2]).isSupersetOf( new Set([1])); // true

// 共通点がない
new Set([1, 2]).isDisjointFrom(new Set([3, 4])); // true
```

残念ながらarrayとの比較はできません。

```js
new Set([1, 2]).intersection([1, 3]); // エラー
```

詳しくは[先日個別に紹介した](https://qiita.com/rana_kualu/items/444cbac3a2ca26152d7a)のでそちらを見てください。


## [RegExp Modifiers](https://github.com/tc39/proposal-regexp-modifiers)

正規表現パターン修飾子の部分適用です。

例として正規表現`/^AB$/i`では`i`がパターン修飾子で、これは大文字小文字を区別しないというフラグです。

これまでは、このパターン修飾子は正規表現全体にしか適用することができませんでした。

今後は、`(?i)`のように書くと、その部分にだけパターン修飾子を適用することができます。

```js
/^A(?i:B)$/.test("Ab"); // true
/^A(?i:B)$/.test("aB"); // false
```

`A(?i:B)`では、`B`にだけパターン修飾子`i`が適用され、`A`には適用されません。
`AB`と`Ab`は通りますが、`aB`・`ab`は通らないということです。

また`(?-i)`と書くと、逆にその部分だけパターン修飾子を打ち消すことができます。

```js
/^A(?-i:B)$/i.test("aB"); // true
/^A(?-i:B)$/i.test("ab"); // false
```

もっと様々な例は[こちら](https://github.com/tc39/proposal-regexp-modifiers/issues/1)にあります。

なお、この書式が可能なパターン修飾子は`i`・`m`・`s`、そして[今後導入される予定の`x`](https://github.com/rbuckton/proposal-regexp-x-mode)です。
`g`・`y`・`u`・`d`に適用することはできません。

しかし`()`だとキャプチャグループやアサーションと見間違えそうですね。
合わせて書きたい場合はどうなるんだろうか。

※2024/01/10修正しました。

<details><summary>修正前、ここは間違いなので注意</summary>

<blockquote>
正規表現のパターン修飾子を追加します。
他言語に存在するものをインポートしたものです。

### i

大文字小文字を区別しないフラグです。

```js
/^AB$/.test("ab");  // false
/^AB$/i.test("ab"); // true
```

もちろん日本語には対応していません。
残念ですね。

```js
/^つ$/i.test("つ"); // true
/^つ$/i.test("っ"); // false
```

### m

複数行マッチ。

`^`は、mがなかったら入力の先頭に一致し、mがあったら行の先頭に一致します。
`$`は、mがなかったら入力の末尾に一致し、mがあったら行の末尾に一致します。

```js
/^ab$/.test("a\nab");  // false
/^ab$/m.test("a\nab"); // true
```

入力値の検証に使う場合は、mを使っていいのはtextareaだけで、それ以外にはこれまでどおりmを使わない、という運用になるでしょう。

### s

`.`はデフォルトだと改行にマッチしませんが、`s`修飾子を入れるとマッチするようになります。

```js
/^a.b$/.test("a\nb");  // false
/^a.b$/s.test("a\nb"); // true
```

これは正直どういうときに使うのかよくわからない。

### x

コメント等の拡張正規表現を書けるようになるらしいです。

```js
/^a(# コメント)b$/.test("ab");  // false
/^a(# コメント)b$/x.test("ab"); // true
```

ここだけ婉曲表現なのは、ChromeもFirefoxも、記事執筆時点では`x`修飾子に対応していなかったので正しいかどうかわからないからです。
</blockquote>

### 部分適用

パターン修飾子を括弧内だけに適用します。

`(?i)`と書くと、その部分だけiを適用することができます。

```js
/^AB$/.test("Ab");      // false
/^A(?i:B)$/.test("Ab"); // true
```

`(?-i)`と書くと、その部分だけiを打ち消すことができます。

```js
/^A(?-i:B)$/i.test("aB"); // true
/^A(?-i:B)$/i.test("ab"); // false
```

ややこしい。

### MDN

x修飾子以外は、MDNとか見てみるとES2025どころか[ずっと昔から](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/RegExp/ignoreCase)[対応している](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/RegExp/dotAll)[ように見える](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/RegExp/multiline)んですよね。
よくわかりません。

</details>


## [Duplicate named capture groups](https://github.com/tc39/proposal-duplicate-named-capturing-groups)

名前付きキャプチャグループで、同じ名前を複数回書けるようになります。

```js
"2025-01-01".match(/(?<year>\d{4})-\d{2}|\d{2}-(?<year>\d{4})/);   // 2025
"01-01-2025".match(/(?<year>\d{4})-\d{2}|\d{2}-(?<year>\d{4})/);   // 2025
```

これまではエラーになっていました。

いったいこれが何の役に立つのかというと、たとえば日付です。
日本では、区切りは"-"・"/"・"年月日"など色々使いますが、順序自体は"2025-01-01"すなわち"Y-m-d"で完全に統一されているのでそもそも他の書き方があるということすら認識されていませんが、たとえばドイツ語での日付表記は"d-m-Y"と真逆の順番になっています。
そのような場合に、どちらの書き方でも正しくyearを取り出すことができるようになるわけです。

なお、両方がマッチする場合は先にマッチした方になるようです。

```js
"2024-01-2025".match(/(?<year>\d{4})-\d{2}|\d{2}-(?<year>\d{4})/); // 2024
```

ちなみにドイツ語の日付表記はEver17で知った。

# 感想

地味に便利な新機能が多いですね。
正直自分がこれらを直接書くかというとあんまり書かないとは思いますが、裏側のライブラリ等で有用に使われそうです。

しかしImport Attributesはぱっと見ただけなのですが、なんか悪いことに使えそうな気がしないでもない。