ES2024で追加される機能について
こんにちは、shibamです。
JavaScriptの標準仕様となるECMAScriptは、ES2015(ES6)以降、毎年バージョンアップが行われています。
今回は今年2月から採択された、ES2024の新仕様についてまとめました。
目次
- はじめに
- TL;DR
- Well-Formed Unicode Strings
- Atomics.waitAsync & Resizable and growable ArrayBuffers
- RegExp v flag with set notation + properties of strings
- Array Grouping
- Promise.withResolvers
- 最後に
1. はじめに
ES2024では、TC39における仕様策定の結果2023年2月~2024年1月の間にFinishedとなったproposalが新機能として採択されます。
今回は、新たに6つの機能が追加されることとなりました。以下で各機能について解説します。
2. TL;DR
String.prototype.isWellFormed()
を使えばサロゲートペアを判断できるようになった
→文字列操作においてサロゲートペアを先に判定しておくのが手軽になりました。- 正規表現において”v”フラグが使えるようになった
→従来の”u”フラグと比較し、絵文字の解析や差集合・積集合などの集合演算解析が行えるようになりました。[ -~]--!?@
みたいな活用が期待できそうです。 Object.Grouping() , Map.Grouping()
によって、配列やオブジェクトのグルーピングが行えるようになった
→言語仕様として採択されたことにより、手軽に・高速にグルーピング処理が行えるようになりました。NoSQLで抜き出した情報に対する操作などで役立ちそうですね。Promise.withResolvers()
によって、PromiseとResolve/Rejectが一括で提供できるようになった。
→Promiseを作成して、Resolve/RejectをPromiseの外側で実行したい時に活用。大きめの非同期処理とかで使えそうですかね。Atomics.waitAsync()
や、ArrayBuffersを拡張可能(あるいは転送可能)とするproposalについては、Authorが同一ですね。WebAssemblyのためのproposalのようです。
3. Well-Formed Unicode Strings
Well-Formed Unicode Strings
"".isWellFormed() //true
のように、対象文字列がサロゲートペアかどうかを判定できるようになる関数です。
サロゲートペアは厄介な問題で…例えば"".length
の実行結果は2になります。
これはlength関数の実装自体が「Unicodeのコードポイントの総数を数える」関数となっていることと、””という字が”0xD842″(先行サロゲート)と”0xDFD7″(後続サロゲート)の2つのUnicodeの組み合わせでできているために発生します。
このような2つのUnicodeの組み合わせによって作られる特殊文字をサロゲートペアと呼んでいます。
絵文字や一部特殊文字などにいたっては、より複数のUnicodeの組み合わせで1つの文字を形成するパターンもあり、これらの問題によって「文字数を正確に計算する」のは意外と困難です。
その結果はsubstring関数などの文字列操作に関しても影響があり、例えば"".substring(0,1)
の結果は’\0xD842’となってしまいます。
結局のところJavaScriptの内部的にはUnicodeの羅列で文字列を見ているため、既存の文字列関数だけだと中々この問題に対処できません。
とはいえ、「サロゲートペアの含まれていない文字列」に対して上記を考慮した関数を実行した場合、無駄に時間がかかってしまい効率が落ちてしまいます。
そこで、今回の関数が役に立ってくる、という感じですね。
(とはいえ、Intl.Segmenter()
使えばいいんじゃないか…という考えもあります。非対応ブラウザ(IEやFireFox)を気にしないのであれば、こちらの方が良いかもしれませんね。)
4. Atomics.waitAsync & Resizable and growable ArrayBuffers & ArrayBuffer transfer
Atomics.waitAsync
Resizable and growable ArrayBuffers
ArrayBuffer transfer
WebAssemblyにおいて、複数個のスレッド間で値をやり取りするためにはShared ArrayBuffersを利用します。
各スレッドは、送信されたShared ArrayBuffersの値を(Unit8ArrayなどのTypedArrayを通して)参照・操作します。
当然このままだとスレッドアンセーフなので、AtomicsAPIを介して上記ArrayBuffersを操作することでスレッドセーフを実現しています。
Atomics API自体は、共有メモリ上の値の操作を、各スレッドの操作状態を確認しながら実行してくれます。
値の操作が完了するまで待機するためにはAtomics.wait()
を実行する必要があるのですが、その非同期版がAtomics.waitAsync()
ですね。
また、上記Shared ArrayBuffersは今まで固定長だったのですが、これを可変長にするための実装がgrowable ArrayBuffersのproposalのようです。
WebAssembly周り、触ったことが無いのでどれほど利点があるのかはわかりませんが。。
5. RegExp v flag with set notation + properties of strings
RegExp v flag with set notation + properties of strings
正規表現による検索時に、vフラグを利用することでより便利に正規表現を書くことができるようになります。
まだ使いこなせていないのですが、大きく以下のような効用があるみたいですね。
-
- Unicode文字列プロパティに対応。複数コードポイントからなる文字列を適切に処理できるようになった。
ES2018から、uフラグを利用した際にUnicode文字プロパティが利用できるようになりました。
これによって例えば/[0-9]/
を/\p{N}/u
と表現できるようになるなど、汎用的な表現をより簡易に行えます。
vフラグからはUnicode文字列プロパティに対応しました。これによって、下図のように複数コードポイントの絵文字にも対応できるようになります。
前述のisWellFormed()もそうでしたが、今回の更新で複数コードポイントの文字列の取り扱いがやりやすくなりそうですね。
-
- 積集合・差集合が扱えるようになり、正規表現の幅が広がった
vフラグにおいて、積集合・差集合が扱えるようになりました。差集合については、「記号のうち、@だけは許可したい」みたいな場面において、/[[\{P}\{S}]--@]/v
のような記法が行えるようになります。
積集合については、例えば「文字集合のうち、ASCIIコードに存在するもののみ抽出」とかしたい場合に、/[\p{L}&&\p{ASCII}]
のような形で表現することができるようになります。
6. Array Grouping
Array Grouping
ArrayやObjectに対してグルーピング処理が行えるようになりました。
JavaでいうところのCollectors.groupingBy()
ですね。
使い道も同じ感じでしょう。バックエンドの処理をNode.jsが担うような構造が増えた分、こういった処理が行える関数がESにも採用されるようになったんでしょうか。
NoSQLが絡むと「ざっくり取って後で選別」のような形になることが多いので、そういう時の痒い所に手が届く関数にはなりそうですね。
サンプルコードとしては、以下のような形になります。
const users = [
{ "名前": "アリス", "年齢": 23, "性別": "女", "趣味": "テニス" },
{ "名前": "ボブ", "年齢": 35, "性別": "男", "趣味": "将棋" },
{ "名前": "チャーリー", "年齢": 20, "性別": "男", "趣味": "将棋" },
{ "名前": "デイヴ", "年齢": 25, "性別": "男", "趣味": "麻雀" },
{ "名前": "エレン", "年齢": 29, "性別": "女", "趣味": "麻雀" },
{ "名前": "フランク", "年齢": 40, "性別": "男", "趣味": "テニス" }
]
Object.groupBy(users, ({ 趣味 }) => 趣味)
/* 実行結果
{
テニス:[
{名前: 'アリス', 年齢: 23, 性別: '女', 趣味: 'テニス'},
{名前: 'フランク', 年齢: 40, 性別: '男', 趣味: 'テニス'}
],
将棋:[
{名前: 'ボブ', 年齢: 35, 性別: '男', 趣味: '将棋'},
{名前: 'チャーリー', 年齢: 20, 性別: '男', 趣味: '将棋'}
],
麻雀:[
{名前: 'デイヴ', 年齢: 25, 性別: '男', 趣味: '麻雀'},
{名前: 'エレン', 年齢: 29, 性別: '女', 趣味: '麻雀'}
]
}
*/
Object.groupBy(users, ({ 名前, 年齢, 性別 }) => {
if (年齢 > 30) {
return "対象者"
} else if (年齢 >= 25 && 性別 === "男") {
return "対象者"
} else {
return "対象外"
}
})
/* 実行結果
{
対象者:[
{名前: 'ボブ',年齢: 35,性別: '男',趣味: '将棋'},
{名前: 'デイヴ',年齢: 25,性別: '男',"趣味": '麻雀'},
{名前: 'フランク', 年齢: 40, 性別: '男', 趣味: 'テニス'}
],
対象外:[
{名前: 'アリス', 年齢: 23, 性別: '女', 趣味: 'テニス'},
{名前: 'チャーリー', 年齢: 20, 性別: '男', 趣味: '将棋'},
{名前: 'エレン', 年齢: 29, 性別: '女', 趣味: '麻雀'}
],
}
*/
7. Promise.withResolvers
Promise.withResolvers
const {promise,resolve,reject} = Promise.withResolvers()
と宣言することで、1つのPromiseに対するresolve,rejectを別に渡せるようになりました。
これによって、従来は
let resolve,reject;
const promise = new Promise((res,rej) => {
resolve = res;
reject = rej;
})
と宣言しないといけなかった所を1行で済ませることができるようになります。
非同期処理を書き進めるにあたって、return new Promise((res,rej) => ...)
と記法するとき、中身が肥大化すると「どこで終わるのか、どう終わるのか」の判断が難しくなってしまうことがあります。
これを解消するためには、上記のようにresolve,rejectを一旦外出しした上で処理を記述する方がスッキリします。
これを以下のように書ける、というのが今回の関数になります。
const {promise,resolve,reject} = Promise.withResolvers();
someFunction().then(res => resolve(res.data)).catch(err => reject(err.message));
return promise;
(jQuery風に言えば、$.Deffered
をイメージしてもらえれば良いかと思います)
地味ですがletに頼らずconstでできるのもうれしいですね。
8. 最後に
vフラグによって、正規表現の記法については変化が起こりそうですね。そもそもUnicode文字プロパティ自体を普段利用していなかったので、まずはそこから押さえなおそうと思います。
また、Object.groupBy()やPromise.withResolvers()
の実装はかゆい所に手が届きそうで良いですね。
革新的ではなが便利な関数が増えたという感じの更新なので、是非活用していきたいと思います。