ECMAScriptの最新動向 2022年06月版
Published on
この記事では2022年06月06日~09日に開催された TC39 meeting 90th で議題に上がったプロポーザルを紹介します。
For Stage 4
findLast / findLastIndex
Stage 4 に到達しました
findLast と findLastIndex は、Array.prototype.find と Array.prototype.findIndex の逆から走査するバージョンです。
const arr = [
{ prop1: 1, prop2: "foo" },
{ prop1: 1, props2: "bar" },
];
const obj1 = arr.find((element) => element.prop1 === 1);
console.log(obj1.prop2); // "foo"
const obj2 = arr.findLast((element) => element.prop1 === 1);
console.log(obj2.props); // "bar"
このミーティングの時点で V8、JavaScriptCore(フラグ付き)、ChakraCore(フラグ付き) にはすでに実装されており、SpiderMonkey でも後日実装されたようです。
(余談ですが筆者は、このプロポーザルのスライド資料を読んで ChakraCore の開発が未だに継続されていることと TC39 プロセスにおける実装の一つとして数えられていることをはじめて知りました。)
For Stage 3
Symbols as WeakMap keys
Stage 3 に到達しました
Symbols as WeakMap keys は WeakMap のキーとして Symbol を使えるようにするプロポーザルです。
提案の概要については
を見てください。
これまでの流れについては
を見てください。
Symbols as WeakMap keys が一見シンプルながらこれまで Stage 2 のままだったのは、グローバルシンボルレジストリに登録された symbol や well-known symbols などのいわゆる eternal symbol を WeakMap のキーとして許容するかどうか、という論点のためでした。
スライド( http://www.rricard.me/serve/tc39-jun2022-symbols-as-wm-keys.pdf ) 上では次のように書かれています。
- 通常の Symbol コンストラクタで作られる unique symbols は WeakMap のキーとして許容する
Symbol.for("...")で作られるグローバルシンボルレジストリに登録された registered symbols は WeakMap のキーとして許容しない- well-known symbols は WeakMap のキーとして許容する
実際 https://tc39.es/proposal-symbols-as-weakmap-keys/ を見る限り WeakMap のキーとして妥当かどうかを判断するために使われている abstract operation CanBeHeldWeakly では registerd symbols を許容していないようです。
RegExp Modifiers
Stage 3 に到達しました
RegExp Modifiers は正規表現パターンの中でのフラグの変更を可能にするプロポーザルです。
概要については 2021 年 12 月の記事を見てください。
JSON.parse source text access
仕様のテキストの修正を待って条件付き Stage 3 にるようです
JSON.parse source text access は、JSON.parse の第2引数として渡すことができる関数(reviver)の中でもとのテキストにアクセスできるようにするためのプロポーザルです。
reviver ではパースした JSON のそれぞれのメンバーのキーとバリューにアクセスし、結果のオブジェクトを返す前になんらかの変換を施すことができます。
const obj = JSON.parse(`{ "foo": 3 }`, (key, value) => {
if (typeof value === "number") {
return value + 2;
}
return value;
});
console.log(obj);
// { "foo": 5 }
この reviver の引数としてキーとバリューが渡ってきた時点ですでにもとのテキストには存在する情報が失われていることがあります。
たとえば { "key": 999999999999999999 } という JSON を JSON.parse でパースするときに、reviver で受け取った引数をそのまま出力する例を考えてみましょう。
JSON.parse(`{ "key": 999999999999999999 }`, (key, value) => {
console.log(value);
return value;
});
// 1000000000000000000
もとの値は 999999999999999999 なのに実際に出力された値は 1000000000000000000 になっています。つまり、reviver の引数として受け取った時点でですでに 999999999999999999 ではなく 1000000000000000000 になってしまっています。
なので、渡されたメンバーのバリューが 999999999999999999 なのかそれとも 1000000000000000000 なのか、reviver 側からは判断ができません。
これでは困るので JSON.parse source text access では、次のようなインターフェースでもとのソーステキストにアクセスできます。
JSON.parse(
`{ "key": 999999999999999999 }`,
(key, value, { source }) => {
// ここで source には '999999999999999999' という文字列が入ってる
...
}
);
For Stage 2
String.dedent
Stage 2 に到達しました
String.dedent はテンプレートリテラルの内部のインデントを適切に除去するためのタグ付きテンプレートリテラルを追加するプロポーザルです。
class MyClass {
print() {
console.log(`
create table student(
id int primary key,
name text
)
`);
}
}
このようなコードを書くと、実際に出力される文字列には、通常プログラマーが期待するものとは異なるスペースが含まれることになります。
const instance = new MyClass();
instance.print();
/*
create table student(
id int primary key,
name text
)
*/
こういうときのために適切にインデントを取り除いてくれるのが String.dedent です。
class MyClass {
print() {
console.log(String.dedent`
create table student(
id int primary key,
name text
)
`);
}
}
const instance = new MyClass();
instance.print();
/*
create table student(
id int primary key,
name text
)
*/
Grouped and Auto-Accessors
Stage 2 に到達しませんでした
Grouped and Auto-Accessors はクラスのアクセサを定義する新しい方法を導入するプロポーザルです。
まず Grouped は次のようにして一つのプロパティのアクセサをまとめて定義できます。
class C {
accessor x {
get() {...}
set() {...}
}
}
const obj = {
accessor x {
get() {...}
set() {...}
}
}
そして Auto-Accessors は Grouped に対するシンタックスシュガーのようなものです。
class C {
accessor a = 1; // `accessor a { get; set; } = 1` と同じ
}
プロポーザルに README では様々なパターンが紹介されているので興味がある人はそちらを参照してください。
For Stage 1
Duplicate named capture groups
Stage 2 に到達しました
この提案はもともと Stage 0 でしたが、アジェンダ上の議題は
Duplicate named capture groups for stage 1, 2, or 3 reaches Stage 2
でした。そして実際に Stage 1 をスキップして Stage 2 に到達しました。
Duplicate named capture groups は正規表現の中で同名の名前付きグループを複数記述可能にするためのプロポーザルです。
次のコードを見てください。
str.match(/(?<year>[0-9]{4})-[0-9]{2}|[0-9]{2}-(?<year>[0-9]{4})/);
これは yyyy-MM もしくは MM-yyyy の形をした文字列のマッチする正規表現ですが、現在の ECMAScript としてはインバリッドです。なぜなら同じ正規表現の中で同名の名前付きキャプチャグループが複数存在するからです。
このようなケースでは、同じ正規表現の中に同名の名前付きキャプチャグループを複数記述できると便利です。
this parameter
Stage 1 に到達しませんでした
this parameter は、TypeScript の this parameter のような構文を JavaScript に導入するためのプロポーザルです。
次のようなモチベーションがあるようです。
- 様々なツールチェインのために TypeScript の
thisparameter を標準化すること - JavaScript と TypeScript の間のギャップを埋めることで初学者にとって易しくなるため
- Type Annotations によってもたらされる負担をへらすため
- メソッドの構文を提供するため
現在 Stage 1 の call-this においては、this を明示できるほうがわかりやすいのかもしれません。
function toHex(this) {
return this.toString(16)
}
42~>toHex()
RegExp Atomic Operators
Stage 1 に到達しました
RegExp Atomic Operators はバックトラックを制御するための新しい構文を正規表現に追加するプロポーザルです。
たとえば /a(bc|b)c/ という正規表現は "abcc" にも "abc" にもマッチします。
"abcc" のときは単純で、まず先頭の a がマッチし、次に (bc|b) の bc にマッチして、最後に c がマッチします。
一方で "abc" のときはやや複雑です。まず先頭の a がマッチし、次に (bc|b) の bc にマッチしますが、そうすると最後の c にはマッチできません。そこで (bc|b) までもどります。前回のマッチングにおいて bc ではマッチできなかったので、もう一つの選択肢である b にマッチさせます。そして最後の c にマッチします。
このような後続のパターンがマッチしない場合に一つ前のパターンに戻ってマッチを試みることをバックトラックといいます。RegExp Atomic Operators はこのようなバックトラックを制御するための構文を追加します。
たとえば前述の /a(bc|b)c というパターンでバックトラックが発生しないように Atomic Operators を使って書くと /a(?>(bc|b))c になります。このパターンでは "abcc" にはマッチしますが、(bc|c) へのバックトラックが発生しないため "abc" にはマッチしません。
他にもいくつかの新しい構文があるみたいなので興味がある人はプロポーザルの README かスペックテキストを参照してください。
Updates
ステージの移動はないものの更新が紹介された提案を列挙します。ここでは詳細については説明しませんがスライドや関連するIssueへのリンクを貼ったので興味のある人は参照してください。
Array Grouping
Array Grouping は Lodash の groupBy のように配列をグルーピングするメソッドを導入するプロポーザルです。
今回の変更で groupBy と groupByToMap から group と groupToMap へとメソッドの名前が変更されました。
詳細は該当の Pull Request( https://github.com/tc39/proposal-array-grouping/pull/39 )を見てください。
Decorators
Shadow Realms
Temporal
function.sent
Import Reflection
参考リンク
記事に関する報告などはこちらから