ES2025 / ES2024 / ES2023 / ES2022
JavaScriptの仕様は、TC39というところで決められています。
ブラウザベンダや関係者が定期的に会合を行い、様々な新機能について話し合って今後のJavaScriptの方向性を決めていきます。
ここでは2024年にFinishedになった、すなわち仕様が確定して複数のブラウザで実装がなされたproposalについて紹介してみます。
主要ブラウザでは既にほとんどの機能を使用可能です。
ちなみに、2024年2月から2025年1月までにStage4になったproposalがES2025と呼ばれます。
ややこしいですね。
Finished Proposals
Promise.try
同期かもしれないし非同期かもしれない、Promiseを返すかもしれないしそうでないかもしれない、そんな関数があります。
その関数を使う際に、いちいち区別して書きたくないから一括でどうにかできない?
function functionAsync(){
return new Promise((resolve, reject) => {});
}
function functionSync(){
return 'hoge';
}
functionAsync().then(); // うごく
functionSync().then(); // エラー
こうすれば一括で動かすことができます。
Promise.resolve().then(functionAsync);
Promise.resolve().then(functionSync);
ただし、これは不必要に非同期で動くので遅くなるらしいです。
何言ってるのかよくわからない。
遅延なく実行させたい場合は、こう書かないといけません。
new Promise(resolve => resolve(functionAsync()));
new Promise(resolve => resolve(functionSync()));
しかしこれは文法もわかりにくいし、どうしてこんな回りくどい書き方をしているのかも理解しがたいですね。
そこでPromise.try()
です。
Promise.try(functionAsync);
Promise.try(functionSync);
どんな関数も統一的な書式で楽観的に同期的に安全に処理できました。
めでたし。
同じ機能を実現するライブラリ[p-try]はなんと100億回以上ダウンロードされています。
Sync Iterator helpers
JavaScriptのIteratorまわりについては、JavaScriptのIterator / Generatorの整理に詳しく書かれています。
_人人人人人人人人人人人人_
> 全く意味がわからない <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^ ̄
まあともかく、なんか適当なIteratorがあったとします。
const iter = [1,2,3,4,5][Symbol.iterator]();
for (value of iter) {
console.log(value); // 1,2,3,4,5
}
これをループを始める前に外から触れるようになります。
// 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については対応していません。
元々はひとつのproposalだったのですが諸事情で分割されたみたいで、分割されたほうのproposalはまだStage2.7です。
Import Attributes
外部ファイルを読み込めるようになります。
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されていました。
しかし、これ相手がJSONのふりしてJavaScriptを返してきたら脆弱性になるだろうという指摘が入り、なんか無駄に長い議論の末に読み込み側が型を指定することに決まりました。
どうしてこの程度のことを誰も最初に突っ込まなかったのかが謎過ぎる。
あとproposal名がModule attributesからImport assertionsになってさらにImport attributesになったり、属性名がwithからassertになってさらにwithに戻ったりと、やたら紆余曲折が激しいproposalでもありました。
JSON Modules
上記Import AttributesのJSONだけについてのproposalです。
import json from "./foo.json" with { type: "json" };
import("foo.json", { with: { type: "json" } });
元々はImport Attributesと一体だったのが分離されたものです。
結局同じ文法で同時にStage4入りしたので、結果的にはひとつのproposalとして扱われたと考えていいかもしれません。
New Set methods
Setに集合演算メソッドが幾つか増えます。
// 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との比較はできません。
new Set([1, 2]).intersection([1, 3]); // エラー
詳しくは先日個別に紹介したのでそちらを見てください。
RegExp Modifiers
正規表現のパターン修飾子を追加します。
他言語に存在するものをインポートしたものです。
i
大文字小文字を区別しないフラグです。
/^AB$/.test("ab"); // false
/^AB$/i.test("ab"); // true
もちろん日本語には対応していません。
残念ですね。
/^つ$/i.test("つ"); // true
/^つ$/i.test("っ"); // false
m
複数行マッチ。
^
は、mがなかったら入力の先頭に一致し、mがあったら行の先頭に一致します。
$
は、mがなかったら入力の末尾に一致し、mがあったら行の末尾に一致します。
/^ab$/.test("a\nab"); // false
/^ab$/m.test("a\nab"); // true
入力値の検証に使う場合は、mを使っていいのはtextareaだけで、それ以外にはこれまでどおりmを使わない、という運用になるでしょう。
s
.
や*
はデフォルトだと改行にマッチしませんが、s
修飾子を入れるとマッチするようになります。
/^a.b$/.test("a\nb"); // false
/^a.b$/s.test("a\nb"); // true
これは正直どういうときに使うのかよくわからない。
x
コメント等の拡張正規表現を書けるようになるらしいです。
/^a(# コメント)b$/.test("ab"); // false
/^a(# コメント)b$/x.test("ab"); // true
ここだけ婉曲表現なのは、ChromeもFirefoxも、記事執筆時点ではx
修飾子に対応していなかったので正しいかどうかわからないからです。
部分適用
パターン修飾子を括弧内だけに適用します。
(?i)
と書くと、その部分だけiを適用することができます。
/^AB$/.test("Ab"); // false
/^A(?i:B)$/.test("Ab"); // true
(?-i)
と書くと、その部分だけiを打ち消すことができます。
/^A(?-i:B)$/i.test("aB"); // true
/^A(?-i:B)$/i.test("ab"); // false
ややこしい。
MDN
x修飾子以外は、MDNとか見てみるとES2025どころかずっと昔から対応しているように見えるんですよね。
よくわかりません。
Duplicate named capture groups
名前付きキャプチャグループで、同じ名前を複数回書けるようになります。
"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を取り出すことができるようになるわけです。
なお、両方がマッチする場合は先にマッチした方になるようです。
"2024-01-2025".match(/(?<year>\d{4})-\d{2}|\d{2}-(?<year>\d{4})/); // 2024
ちなみにドイツ語の日付表記はEver17で知った。
感想
地味に便利な新機能が多いですね。
正直自分がこれらを直接書くかというとあんまり書かないとは思いますが、裏側のライブラリ等で有用に使われそうです。
しかしImport Attributesはぱっと見ただけなのですが、なんか悪いことに使えそうな気がしないでもない。