【JavaScript】ES2024の新機能まとめ


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


JavaScriptの仕様は、[TC39](https://tc39.es/process-document/)というところで決められています。
ブラウザベンダや関係者が[定期的に会合を行い](https://github.com/tc39/notes/tree/main/meetings)、様々な新機能について話し合って[どのようにするかを決めています](https://github.com/tc39/agendas)。

ちなみに[2023年9月のミーティング](https://github.com/tc39/notes/blob/main/meetings/2023-09/september-26.md)は[東京で行われました](https://ecma-international.org/news/september-25th-javascript-community-event-in-tokyo-japan/)。


ここでは2023年に[Finishedになった](https://github.com/tc39/proposals/blob/main/finished-proposals.md)、すなわち仕様が確定して複数のブラウザで実装がなされたproposalについて紹介してみます。
つまり、主要ブラウザでは既に使用可能です。

なお、2023年2月から2024年1月までにFinishedになったproposalが`ES2024`と呼ばれるみたいです。
ずれているせいでややこしいですね。
年と一致させてくれ。


# Finished Proposals


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

Promiseを外からresolve/rejectする方法を提供します。

```js
const { promise, resolve, reject } = Promise.withResolvers();
promise
  .then((value)   => console.log("resolveされた", value))
  .catch((reason) => console.error("rejectされた", reason));

if(なんか){
    resolve(); // "resolveされた"
}else{
    reject();  // "rejectされた"
}
```

`Promise.withResolvers()`は`{promise, resolve, reject}`を返すので、その後はいつでも好きにresolve/rejectできます。

これまでも`const promise = new Promise((res, rej) => { resolve = res; reject = rej; });`みたいなかんじに代入することで[同じことはできていた](https://zenn.dev/sosukesuzuki/articles/41721cbbb86ff5)のですが、[車輪](https://github.com/facebook/react/blob/d9e0485c84b45055ba86629dc20870faca9b5973/packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js#L95)[の再](https://github.com/vuejs/core/blob/9c304bfe7942a20264235865b4bb5f6e53fdee0d/packages/runtime-core/src/compat/componentAsync.ts#L32)[発明](https://github.com/axios/axios/blob/bdf493cf8b84eb3e3440e72d5725ba0f138e0451/lib/cancel/CancelToken.js#L20)[が過](https://github.com/microsoft/TypeScript/blob/1d96eb489e559f4f61522edb3c8b5987bbe948af/src/harness/util.ts#L121)[ぎる](https://github.com/vitejs/vite/blob/134ce6817984bad0f5fb043481502531fee9b1db/playground/test-utils.ts#L225)[ので](https://deno.land/std@0.178.0/async/deferred.ts?source)実装したとのことです。
便利なシンタックスシュガーですね。


## [Resizable and growable ArrayBuffers](https://github.com/tc39/proposal-resizablearraybuffer)

ArrayBufferをリサイズ可能にします。

[ArrayBuffer](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer)は固定長です。
サイズを変更する場合は新たなArrayBufferを作ってコピーするという運用になっています。
これがWebAssembly等で[問題になっていました](https://github.com/WebAssembly/design/issues/1296)。

ということで、生成されたArrayBufferを作りなおさずにサイズを変更可能にするオプションが追加されました。

```js
// 初期値8、最大値16
let rab = new ArrayBuffer(8, { maxByteLength: 16 });
rab.resizable; // true
let U32a = new Uint32Array(rab);
U32a.byteLength; // 8

// 16まで変更可能
rab.resize(16);
U32a.byteLength; // 16

// 範囲外
rab.resize(32); // Uncaught RangeError: Invalid length parameter

// 未指定時は変更不可
let rab = new ArrayBuffer(8);
rab.resizable; // false
```

`ArrayBuffer`のコンストラクタに`maxByteLength`を渡すと、その値までの範囲で`ArrayBuffer`のサイズが変更可能になります。
引数を渡さない場合はこれまでどおり変更不可です。
また`ArrayBuffer`に読み取り専用プロパティ`resizable`、`maxByteLength`等が追加されます。
`resizable`はサイズを変更可能かどうか、`maxByteLength`は変更できる最大サイズを取得できます。

あとproposalでは[SharedArrayBuffer](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer)にも適用されると書いてあるのですが、こいつは[Spectre](https://meltdownattack.com/)対策で使用するためには[面倒な対応が必要](https://blog.agektmr.com/2021/11/cross-origin-isolation.html)です。
一般的環境では`SharedArrayBuffer is not defined`となって使えないところがほとんどなので、あまり気にする必要はないでしょう。
ちなみに`ArrayBuffer`は`resize()`で任意にサイズの拡縮が可能ですが、`SharedArrayBuffer`は`grow()`であり、サイズは増加させることしかできません。


## [Array Grouping](https://github.com/tc39/proposal-array-grouping)

配列をグルーピングするメソッド`Object.groupBy`と`Map.groupBy`を追加します。

```js
let array = [1, 2, 3, 4, 5];

Object.groupBy(array, (num, index) => {
  return num % 2 === 0 ? "even": "odd";
});
/*
{
    "odd": [
        1,
        3,
        5
    ],
    "even": [
        2,
        4
    ]
}
*/

const odd  = { odd: true };
const even = { even: true };
Map.groupBy(array, (num, index) => {
  return num % 2 === 0 ? even: odd;
});
/*
new Map([
    [
        {
            "odd": true
        },
        [
            1,
            3,
            5
        ]
    ],
    [
        {
            "even": true
        },
        [
            2,
            4
        ]
    ]
])
*/
```

`Object.groupBy`はオブジェクトに、`Map.groupBy`はMapに値をグループ化してくれます。
RFCの例では偶数奇数の2つだけですが、もちろん3つ以上のグループに分けることも可能です。
これを使うことで、たとえば年齢層ごとに集計したりといった分析がやりやすくなることでしょう。
正直自力で実装しても[さほどたいした内容ではない](https://github.com/zloirock/core-js/blob/v3.35.0/packages/core-js/modules/es.map.group-by.js)のですが、ネイティブで実装されていれば性能も高いし安心ですね。

ちなみに、どうして`array.groupBy()`とかではなくわざわざ静的メソッドになっているのかがproposalで解説されています。
当初`Array.prototype.groupBy`にしようとしていたところ、その名前だと[Sugar](https://sugarjs.com/)というライブラリが[動かなくなる](https://github.com/tc39/proposal-array-grouping/issues/37)ことが判明しました。
これで600以上のサイトが影響を受けることがわかったため、名前を`Array.prototype.group`に変更しました。

ところが今度は[LastPass Password Manager](https://addons.mozilla.org/en-US/firefox/addon/lastpass-password-manager/)が[動かなくなりました](https://bugzilla.mozilla.org/show_bug.cgi?id=1791415#c14)。
影響を受けるアプリがひとつだけなら修正してもらうという手もあったかもしれませんが、`group`なんて一般的な名前が衝突しないわけもなく、問題が[他にも](https://github.com/webcompat/web-bugs/issues/112923)[続々と](https://github.com/webcompat/web-bugs/issues/112552)発見されてしまいました。

`group`も使えなくなったので次は`groupToObject`にしよう、いや`partition`だと[様々な議論が行われた](https://github.com/tc39/proposal-array-grouping/issues/44)結果、最終的に[静的メソッド化したプルリクエスト](https://github.com/tc39/proposal-array-grouping/pull/47)がこれいーじゃんと受け入れられました。



## [Well-Formed Unicode Strings](https://github.com/tc39/proposal-is-usv-string)

与えられた文字列が`Well-Formed Unicode String`であるかどうかをチェックする関数`String.prototype.isWellFormed`・`String.prototype.toWellFormed`を追加します。

それで`Well-Formed Unicode String`って何だよという話ですが、[サロゲートペアがきちんとペアじゃないといけない](https://cybozu.github.io/frontend-expert/posts/strings-as-import-export)ということらしいです。
全然わかってないけど、まあ要するにちゃんと正しい文字列であればいいということでしょう、きっと。

![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/1168c7f1-a19f-7286-d1f4-0a551dfe3391.png)

proposal内にも[たった1行でのpolyfill](https://github.com/tc39/proposal-is-usv-string#algorithm)が載っているくらい簡単に代替できるのですが、このpolyfillは全文字をスキャンするのでたいへん遅くなってしまいます。
これをネイティブで実装することによって速度が早く、見た目もわかりやすくなるようです。


## [RegExp v flag with set notation + properties of strings](https://github.com/tc39/proposal-regexp-v-flag)

正規表現にvフラグを追加します。
これは既存のuフラグの拡張版です。
既に[詳しい説明をしている人がいる](https://zenn.dev/pixiv/articles/54e5d29c54e7f5)のでそちらを見てください。
私にはさっぱりわかりません。

```js
/^\p{Emoji}$/.test('👨🏾‍⚕️'); // false
/^\p{RGI_Emoji}$/.test('👨🏾‍⚕️'); // false

/^\p{Emoji}$/u.test('👨🏾‍⚕️'); // false
/^\p{RGI_Emoji}$/u.test('👨🏾‍⚕️'); // Uncaught SyntaxError: Invalid regular expression

/^\p{Emoji}$/v.test('👨🏾‍⚕️'); // false
/^\p{RGI_Emoji}$/v.test('👨🏾‍⚕️'); // true
```

とりあえず[なんか合体する絵文字](https://note.com/ttuusskk/n/n1bff5d8e638c)が正しく検索できるようになると認識した。



## [Atomics.waitAsync](https://github.com/tc39/proposal-atomics-wait-async)

[Atomics.wait](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait)の非同期版です。

そもそも`Atomics.wait`自体[どういうときに](https://qiita.com/shellyln/items/efe0820b1959a40076c3)[使うのか](https://note.affi-sapo-sv.com/js-sharedarraybuffer.php)[わかっていない](https://zenn.dev/sosukesuzuki/articles/a20ec4721c7568)ので、さらにその発展形とか言われてもさっぱりだぜ。


# 感想

私個人としてはあまり使いそうにない機能が多いのですが、これまで無駄な定型文を書いていたところがシンタックスシュガーですっきり書けるようになったり、フロントだけで文字を処理できるようになったりと、フロントエンドエンジニアにとっては有用な機能が揃っていると思います。

それにしても文字コードまわりはいくらドキュメントを読んでもさっぱりわからんな。
やはりさっさとUTF-128化してしまうべきなのでは？