JavaScriptの仕様は、TC39というところで決められています。
ブラウザベンダや関係者が定期的に会合を行い、様々な新機能について話し合って今後のJavaScriptの方向性を決めていきます。
ここでは2025年にFinishedになったproposalについて紹介してみます。
『Finishedになった』の定義は、現在ではChrome・Firefox・Safariのうち2つ以上に実装された、という意味だと思えばだいたい合っています。
すなわち、主要ブラウザでは既にほとんどの機能を使用可能何気にChromeだけ対応してないのが多いです。
Finished Proposals
JSON.parse source text access
JSON.parseの引数reviverに、パラメータcontextを追加します。
JSON.parseは数値型文字列を数値にしたりと一部の型を勝手に変換するので、元の正確な値が失われてしまうことがあります。
const tooBigInt = "100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001";
JSON.parse(tooBigInt); // 1e128
第二引数reviverを使うと変換中の操作に割り込むことができます。
const reviver = (key, val) => {
console.table(val); // 1e128
return "hoge";
}
JSON.parse(tooBigInt, reviver); // "hoge"
ところがreviverに渡ってくる第二引数valは既に変換された後の値なので、元の値を知りたいという役には立ちません。
ということで、変換される前の値を第三引数contextで受け取ることができるようになりました。
const reviver = (key, val, context) => {
console.table(context); // {source:"100000…1"}
return context.source;
}
JSON.parse(tooBigInt, reviver); // "100000…1"
これによって余計な変換をされることなく、正確な値を回収することが可能になります。
めでたし。
Upsert
キーが存在していれば取得し、存在していなかったら値を設定したいという、SQLでいうUPSERTと同じ機能ですね。
let hoge = new Map([["foo", 1], ["bar", 2]]);
// これまで
if(!hoge.has("baz")){
hoge.set("baz", 3);
}
// これから
hoge.getOrInsert("baz", 3);
hoge.getOrInsert("foo", 4); // fooは1のまま
既にキーが存在する場合は上書きされずそのままになります。
なおオブジェクトには使えません。
let hoge = {foo:1, bar:2};
hoge.has("baz"); // hoge.has is not a function
hoge.getOrInsert("baz", 3); // hoge.getOrInsert is not a function
hoge.buz??=3; // 3
null合体代入演算子と使い分ける必要があります。
どうして。
ちなみに同じ機能がJavaではcomputeIfAbsent、ScalaではgetOrElseUpdate、C++ではemplace、C#ではGetOrAdd、Rustではor_insert_with、Pythonではsetdefaultです。
どうしてそんなにバラバラなんですか?
Math.sumPrecise
できるだけ正確な足し算です。
let values = [1e20, 1, -1e20];
values.reduce((a, b) => a + b, 0); // 0
Math.sumPrecise(values); // 1
大きな差のある数を計算すると、小さいほうの値が情報落ちで消滅してしまうことがよくあります。
そのような場合にも、なるべく正確な計算をしてくれる便利関数です。
なお丸め誤差については効かないみたいです。
0.1 + 0.2; // 0.30000000000000004
Math.sumPrecise([0.1, 0.2]); // 0.30000000000000004
Error.isError
なんだよその名前。
引数のエラーが真のエラーだった場合にのみtrueを返します。
真のエラーってなんだよ。
どうもこのproposal説明不足すぎてよくわからないのですが、こんなかんじのようです。
・obj instanceof Errorはクロスレルムで判定に失敗することがあり、また偽造することも可能。
・Object.prototype.toString.callを使った判定もtoStringTag等で偽造可能。
つまり、throwされたErrorが本当にErrorであるかを確実に判定する方法が、実はこれまで存在しませんでした。
ということで確実に判定する方法が追加されたというわけです。
let realError = new Error();
let fakeError = new Map();
fakeError.__proto__ = Error.prototype;
realError instanceof Error; // true
fakeError instanceof Error; // true ←
Error.isError(realError); // true
Error.isError(fakeError); // false
同様のメソッドとしてArray.isArray()があります。
でもたとえばMap.isMap()やIterator.isIterator()等は存在しておらず、そのMapが真のMapか、そのIteratorが真のIteratorかを判定する方法はありません。
個別にメソッドを追加していくのではなく、なんかそういう汎用な、強いinstanceofみたいなのがほしいですよね。
Iterator Sequencing
複数のイテレータを合体させます。
let lows = Iterator.from([0, 1, 2, 3]);
let highs = Iterator.from([6, 7, 8, 9]);
let digits = Iterator.concat(lows, [4, 5], highs); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
単に前から順にくっつけるだけのようです。
let digits = function* () {
yield* lows;
yield 4;
yield 5;
yield* highs;
}();
[0, 6, 1, 7, 2, 8, 3, 9]と交互にくっつける、みたいな機能はないみたいでした。
Uint8Array to/from Base64
バイナリをBASE64に変換します。
// なんかのバイナリ
let arr = new Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]);
// BASE64にする
arr.toBase64(); // SGVsbG8gV29ybGQ=
// HEXにする
arr.toHex(); // 48656c6c6f20576f726c64
// BASE64から元に戻す
Uint8Array.fromBase64("SGVsbG8gV29ybGQ=");
// HEXから元に戻す
Uint8Array.fromHex("48656c6c6f20576f726c64");
これまでは間にatobを挟まないといけなかったのが直接できるようになったのかな。
画像などのバイナリデータをJSから楽に送受信できるってかんじでいいんだろうか。
Array.fromAsync
同期イテレータからはArray.fromでArrayを作成できますが、非同期イテレータからは作成できません。
async function * asyncGen (n) {
for (let i = 0; i < n; i++)
yield i * 2;
}
const arr = await Array.from(asyncGen(4)); // []
どうして。
ということで非同期イテレータからArrayを作成できるようになりました。
const arr = await Array.fromAsync(asyncGen(4)); // [0, 2, 4, 6]
実質↓と同じだそうです。
const arr = [];
for await (const v of asyncGen(4)) {
arr.push(v);
}
arr; // [0, 2, 4, 6]
普通にArray.fromが非同期も受け取れるようにするのでは駄目だったのだろうか。
感想
今年もいろいろな機能が搭載されましたね。
特にJSONのパースには困っていた人がわりかし多いのではないでしょうか。
逆にError.isErrorなんかは、意義はわかるんだけど、実際これがなくて困ってたんだよねーって現場は存在するんだろうか。