JavaScript ES2024の新機能
ECMAScriptはJavaScriptの標準規格で、2015年以降毎年改定されています。この記事ではES2024に採用された仕様、ES2025に決まっている仕様、次に盛り込まれそうな仕様について解説します。

ECMAScriptとは
ECMAScriptとはJavaScriptの標準規格
ECMAScript(エクマスクリプトと読む)とはJavaScriptの標準規格です。 ECMAScriptの背景や策定プロセスについては以前のブログを参考にしてください。
ECMAScriptのバージョン
ECMAScriptのバージョンはedition番号で管理されていて、例えば第5版はES5と呼ばれます。ES6からは毎年仕様が改定されており、ES2015のように発行年つきで呼ぶことが推奨されています。ES6とES2015、ES7とES2016はそれぞれ同じ仕様です。
| バージョン | 年号付きバージョン | 公開日 |
|---|---|---|
| ES | 1997年6月 | |
| ES2 | 1998年6月 | |
| ES3 | 1999年6月 | |
| ES4 | 放棄 | |
| ES5 | 2009年12月 | |
| ES5.1 | 2011年6月 | |
| ES6 | ES2015 | 2015年6月 |
| ES7 | ES2016 | 2016年6月 |
| ES8 | ES2017 | 2017年6月 |
| ES9 | ES2018 | 2018年6月 |
| ES10 | ES2019 | 2019年6月 |
| ES11 | ES2020 | 2020年6月 |
| ES12 | ES2021 | 2021年6月 |
| ES13 | ES2022 | 2022年6月 |
| ES14 | ES2023 | 2023年6月 |
| ES15 | ES2024 | 2024年6月 |
| ES16 | ES2025 | 公開前 |
ECMAScriptの策定プロセス
ES2015以降では、仕様の提案は5段階のステージに分けられ、リリース時期(毎年6月頃)にステージ4にあるものがリリース対象になります。
| ステージ | 状態 | 説明 |
|---|---|---|
| 0 | Strawperson | 仕様の提案 |
| 1 | Proposal | - 変更を適用したいケースの作成 - 解決方法の説明 - 潜在的な変更の適用 |
| 2 | Draft | 文法やその意味を言語仕様のフォーマットで記述 |
| 3 | Candidate | 実装とユーザからのフィードバック待ちの状態 |
| 4 | Finished | 仕様に追加できる状態 |
ES2024
Finishedのステージにあるものから発行年度が2024年のものがES2024になります。
ArrayBuffer transfer
ArrayBufferに2つのメソッドと1つのプロパティが追加されました。
class ArrayBuffer { // ... その他のメソッド等 // 同じバイト配列を持つArrayBufferを返して、 // 元のArrayBufferをdetacheする transfer(newByteLength); // サイズ変更不可能なArrayBufferを返す以外はtransferと同じ transferToFixedLength(newByteLength); // ArrayBufferがdetacheされているかどうか get detached(); }
ArrayBufferはメモリの参照にすぎないので、以下の例のようにタイミングによってはデータが変更されている可能性があります。
function validateAndWrite(arrayBuffer) { // 非同期なvalidationをする await validate(arrayBuffer); // validationが通ったらファイルに書き込む await fs.writeFile("data.bin", arrayBuffer); } const data = new Uint8Array([0x01, 0x02, 0x03]); validateAndWrite(data.buffer); setTimeout(() => { data[0] = data[1] = data[2] = 0x00; }, 50);
こういった場合の対処として、今まではコピーしてつかっていました。
function validateAndWriteSafeButSlow(arrayBuffer) { // まずコピーする const copy = arrayBuffer.slice(); await validate(copy); await fs.writeFile("data.bin", copy); }
上記のコードはコピーするのに時間がかかるし、メモリが倍必要になるという問題がありました。 そこでtransferメソッドが導入されました。
function validateAndWriteSafeAndFast(arrayBuffer) { // 所有権を移動(コピーされたのと同じだが、元のarrayBufferはdetacheされている) const owned = arrayBuffer.transfer(); // arrayBufferはすぐにdetacheされる(メモリから切り離されている) assert(arrayBuffer.detached); await validate(owned); await fs.writeFile("data.bin", owned); }
transferを使用することで、コピーの時間や倍のメモリを必要とせずに安全に処理ができるようになります。
Promise.withResolvers
Promiseをつくるとき、resolveとrejectを引数とするとコールバック関数を引数として渡します。
const promise = new Promise((resolve, reject) => { asyncRequest(config, response => { const buffer = []; response.on('data', (data) => buffer.push(data)); response.on('end', () => resolve(buffer)); response.on('error', reason => reject(reason)); }); }); promise .then((buffer) => nextProcess(buffer)) // promiseが完了したらnextProcess関数を呼ぶ .catch((reason) = console.log(reason);
しかし、例えばresponseのcallback-requestイベントでendイベントの完了を待ってnextProcess関数を呼びたいとしたらどうなるでしょう?
callback-requestイベントのコールバック関数内で、endイベント完了のPromiseを呼び出す必要があります。
この場合、以下のようにPromiseを先に作っておいて後からPromiseのコールバック関数を定義します。
let resolve, reject; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); asyncRequest(config, response => { const buffer = []; response.on('callback-request', id => { promise.then(data => nextProcess(id, data)); // callback-requestイベントがいつ来ても、endイベントの完了を待って処理する }); response.on('data', data => buffer.push(data)); response.on('end', () => resolve(buffer)); response.on('error', reason => reject(reason)); });
このようにイベントの待ち合わせをするケース等、Promiseをインスタンス化した後でresolve/rejectを使いたいという場合があります。
その場合に使えるのがPromise.withResolvers()で、具体的には上記のコードの最初の5行をまとめてやってくれるメソッドです。
const { promise, resolve, reject } = Promise.withResolvers(); // 以下のコードと同じ // let resolve, reject; // const promise = new Promise((res, rej) => { // resolve = res; // reject = rej; // });
Array Grouping
ObjectとMapにgroupByメソッドが追加されました。
それぞれArrayを分類して、ObjectやMapに変換して返してくれます。
const array = [1, 2, 3, 4, 5]; // `Object.groupBy` は任意のキーで分類します Object.groupBy(array, (num, index) => { return num % 2 === 0 ? 'even': 'odd'; }); // => { odd: [1, 3, 5], even: [2, 4] } // `Map.groupBy` はMapで返し、キーにオブジェクトが使えます // using an object key. const odd = { odd: true }; const even = { even: true }; Map.groupBy(array, (num, index) => { return num % 2 === 0 ? even: odd; }); // => Map { {odd: true}: [1, 3, 5], {even: true}: [2, 4] }
ArrayBufferのサイズ変更機能
ArrayBuffer のサイズを動的に変更することができるようになりました。コンストラクタでmaxByteLengthを指定することで変更可能となります。
const buffer = new ArrayBuffer(8, { maxByteLength: 16 }); // 最大16バイトまで拡張可能な8バイトのバッファを作成 buffer.resize(12); // バッファのサイズを12バイトに変更 console.log(buffer.resizable); // true console.log(buffer.maxByteLength); // 16
WebAssembly(Wasm)では、メモリが ArrayBuffer を介して管理されます。従来、Wasm のメモリを拡張する際には新しい ArrayBuffer を作成し、古いものをデタッチする必要がありました。しかし、ES2024 の新機能により、ArrayBuffer をインプレースでリサイズできるため、メモリ管理がより効率的かつ簡潔になりました。
// Wasm メモリのバッファを取得 let wasmMemoryBuffer = wasmInstance.exports.memory.buffer; // サイズ変更可能か確認 if (wasmMemoryBuffer.resizable) { // 必要に応じてサイズを拡張 wasmMemoryBuffer.resize(newSize); }
このように、ArrayBuffer の新機能を活用することで、Wasm と JavaScript 間のメモリ操作がよりシームレスになります。
SharedArrayBufferも同様にサイズ拡張が可能になっています。
const sab = new SharedArrayBuffer(1024, { maxByteLength: 2048 }); console.log(sab.maxByteLength); // 2048 console.log(sab.growable); // true sab.grow(512); // 現在のサイズから512バイト増加 console.log(sab.byteLength); // 1536
RefExp vフラグ
RefExpにvフラグが追加されました。ES2015でuフラグが追加されて正規表現がコードポイントで指定できるようになりましたが、それを拡張するものになります。
(1) Properties of Stringsの対応
const regexGreekSymbol = /\p{Script_Extensions=Greek}/u; regexGreekSymbol.test('π'); // → true
ES2018のUnicode 文字クラスエスケープ\pとUnicodeプロパティを使って以下のような正規表現がつくれるのですが、絵文字は複数のコードポイントをもつことがあるのでうまくいきません。
// Emoji = 絵文字のUnicodeプロパティ const re = /^\p{Emoji}$/u; // コードポイントが1つの場合 re.test('⚽'); // '\u26BD' // → true ✅ // 複数のコードポイントを持つ場合 re.test('👨🏾<200d>⚕️'); // '\u{1F468}\u{1F3FE}\u200D\u2695\uFE0F' // → false ❌
Unicode にはいくつかの Properties of Stringsが定義されているので、これをvフラグで取り入れました。
// RGI_Emoji = 絵文字のUnicode Properties of Strings const re = /^\p{RGI_Emoji}$/v; re.test('⚽'); // '\u26BD' // → true ✅ re.test('👨🏾<200d>⚕️'); // '\u{1F468}\u{1F3FE}\u200D\u2695\uFE0F' // → true ✅
(2) 論理演算
これまでのUnicodeプロパティと今回対応されたProperties of Stringsに対して、論理演算が行えるようになりました。
// 交差 Intersection && const regex = /[\p{Alphabetic}&&\p{Number}]/v; console.log(regex.test("A")); // false(Aはアルファベットだが数字ではない) console.log(regex.test("1")); // false(1は数字だがアルファベットではない) // 差分 Subtraction -- const regex = /[\p{Script=Latin}--\p{Number}]/v; console.log(regex.test("A")); // true(Aはラテン文字) console.log(regex.test("1")); // false(1はラテン文字に含まれるが、Number にも該当するので除外される) // 和集合 Union || const regex = /[\p{Script=Latin}||\p{Script=Greek}]/v; console.log(regex.test("A")); // true(ラテン文字) console.log(regex.test("Γ")); // true(ギリシャ文字) console.log(regex.test("あ")); // false(ひらがなはマッチしない)
Atomics.waitAsync
Atomics.waitの非同期版で共有メモリ上の特定の位置で非同期的に待機するために使用されます。メインスレッドのように(Atomics.wait() のような)ブロッキング操作が許可されていない環境で有用です。
メインスレッド側
// 共有メモリの作成(4バイト = Int32 1つ) const sharedBuffer = new SharedArrayBuffer(4); const int32Array = new Int32Array(sharedBuffer); // Worker を作成 const worker = new Worker("worker.js"); worker.postMessage(sharedBuffer); // 非同期で待機 const result = Atomics.waitAsync(int32Array, 0, 0); if (result.async) { result.value.then((status) => { console.log("Workerからの通知:", status); // "ok" が出力される console.log("計算結果:", int32Array[0]); // 計算後の値を取得 }); // この後も処理は続けられる
Worker側
self.onmessage = (event) => { const sharedBuffer = event.data; const int32Array = new Int32Array(sharedBuffer); // 何らかの計算処理(例: 数値を更新) setTimeout(() => { int32Array[0] = 42; // 計算結果を格納 console.log("Worker: 計算完了"); Atomics.notify(int32Array, 0); // メインスレッドを起こす }, 2000); };
実行結果
(2秒後) Worker: 計算完了 Workerからの通知: ok 計算結果: 42
文字列が「正しい」コードかの判定と修正
JavaScriptの文字列は、16ビットの値(コードユニット)のシーケンスとして表現されます。しかし、すべての16ビット値の組み合わせが有効なUnicode文字を構成するわけではありません。特に、サロゲートペアと呼ばれる特定の範囲のコードユニットは、正しい組み合わせで使用されないと「不正な(ill-formed)」文字列となります。
サロゲートペアとは:
これらは単独では有効なUnicode文字を表さず、リードサロゲートとテールサロゲートが対になって初めて1つのUnicode文字を表現します。したがって、サロゲートペアが正しく組み合わされていない文字列は「不正な」ものと見なされます。
Stringクラスに文字列正しいか判定するisWellFormedメソッドと不正なサロゲートをUnicodeの置換文字(U+FFFD:�)に置き換えるtoWellFormedが追加されました。
const str1 = 'Hello\uD83D\uDE00'; // 正しいサロゲートペアを含む console.log(str1.isWellFormed()); // true const str2 = 'Hello\uD83D'; // 不正なサロゲートを含む console.log(str2.isWellFormed()); // false const str = 'Hello\uD83D'; // 不正なサロゲートを含む const wellFormedStr = str.toWellFormed(); console.log(wellFormedStr); // 'Hello�'




