もうじき2022年ということで、ES2022にFinished Proposalsとして取り入れられた機能がそれぞれどんなものなのか見てみることにします。
ちなみにFinished Proposalの手前の段階であるStage4が「複数の実装が既に存在する」というのが定義なので、2022という名前にもかかわらず既に一部のブラウザで使用可能、というか一部以外のブラウザで使用可能です。
ES2022
Class Fields
Class Fieldsは似たような3RFCの集合体です。
Private instance methods and accessors
privateメソッドです。
むしろ今まで存在してなかったことにびっくりだよ。
class Foo{
#privatemethod() {
return "privatemethod";
}
publicmethod() {
return "publicmethod";
}
}
c = new Foo();
c.publicmethod(); // "publicmethod"
c.privatemethod(); // c.privatemethod is not a function
c.#privatemethod(); // Private field '#privatemethod' must be declared in an enclosing class
他言語のprivateメソッドと同じで、クラス内からは呼び出し可能ですが、クラス外からFoo.#privatemethod()
のようにアクセスすることはできません。
他言語より強力な点として、リフレクションのような手段をもってしてもアクセスすることのできない完全なprivateです。
あとアクセサにも付けられるようになりますが、こちらは正直用途がよくわかりません。
class Foo{
get #x(){return this.value;}
set #x(value){this.value = value;}
}
Class Public Instance Fields & Private Instance Fields
public・privateフィールドです。
むしろ今まで存在してなかったことにびっくりだよ。
class Foo{
x = 1;
#y = 1;
}
publicについては、JSではいつでもどこでもfoo.x = 1
とかpublicフィールドを生やせるので究極的には要らないと言えば要らないのですが、まあ明示できた方がいいですよね。
一方でprivateフィールドは、いくら明示したとしても外からアクセスすることはできません。
ところでこの話が出てきたのってだいぶ前だった気がするのですが、Finishedになったのって今年になってようやくだったんですね。
Static class fields and private static methods
静的フィールド・メソッドです。
むしろ今まで存在してなかったことにびっくりだよ。
class Foo{
static x = 1;
static #y = 2;
static bar(){}
static #baz(){}
}
上で出てきたばかりのprivateフィールドやprivateメソッドにも対応します。
このあたりの機能が出揃ったことによって、素のJavaScriptでもようやくまともなクラス構文が書けるようになりそうですね。
RegExp Match Indices
正規表現マッチングに、マッチした位置の情報を追加します。
const result = /ef(gh)(i)j/d.exec('abcdefghijklmn');
console.table(result.indices);
正規表現にフラグd
を追加すると、返り値にindices
が増えます。
追加が必要なのはパフォーマンス的な理由であり、フラグがない場合はこれまでと同じ挙動になります。
indices
は配列となっていて、0には正規表現全体が文字列にマッチした部分が入ってきます。
例であれば4-10となっていますが、この返り値はslice
でちょうど切り取れる値です。
'abcdefghijklmn'.slice(4, 10); // 'efghij'
1以降はキャプチャグループがあればそのマッチした範囲が入ります。
例であれば1に(gh)
、2に(i)
のキャプチャグループの結果が入っています。
今回の例では実質的に正規表現を使ってないのであまり意味がないですが、キャプチャ文字列長が不定になるような正規表現を使うときなどは特に便利になることでしょう。
Top-level await
async関数の外側でawaitを使えるようになります。
await 1;
これができて何が便利かというと、さくっとexportしたりできます。
export const hoge = await f();
もっと詳しい記事がこちらにあったので、詳細はここなどを見るとよいと思います。
何言ってるのか9割方わかりませんでした。
function f(){
await 1; // Uncaught SyntaxError: await is only valid in async functions
}
なお、asyncでない関数の中では相変わらず使用できません。
なんで。
Ergonomic brand checks for Private Fields
人間工学に基づいたprivateフィールドのチェック
なんのこっちゃ。
in
をprivateフィールドにも使用可能にします。
class Foo{
#privateField = 1;
static isFoo(obj){
return #privateField in obj;
}
}
class Bar{
#privateField = 1;
}
Foo.isFoo(new Foo()); // true
Foo.isFoo(new Bar()); // false
当然privateフィールドは外から見えないので、自身のクラス内でしか使用できません。
つまり、これとだいたい同じです。
class Foo{
#privateField = 1;
static isFoo(obj){
return obj instanceof Foo;
}
}
細かいところで動作が異なるらしいのですが、そこまでしてこの機能を必要とする理由はよくわかりませんでした。
.at
全てのインデックス可能なクラスArray・String・TypedArray
に、指定位置の値を取得するメソッド.at()
を追加します。
'abcdef'.at(0); // a
'abcdef'.at(1); // b
'abcdef'.at(-1); // f
'abcdef'.at(-2); // e
[1, 2, 3].at(0); // 1
[1, 2, 3].at(1); // 2
[1, 2, 3].at(-1); //3
指定位置の値を返します。
他言語でもよくあるやつです。
残念ながら引数はひとつだけで、'abcdef'.at(1, 3) // 'bcd'
みたいに範囲を取得することはできません。
一見[]
と同じやん、と思いますが、実は[]
は負の値を使うことができません。
'abcdef'[0]; // a
'abcdef'[1]; // b
'abcdef'[-1]; // undefined
というより、[]
は単にオブジェクトのプロパティにアクセスしているだけであって、たとえばfoo[-1]
はfoo
オブジェクトのプロパティ-1
を参照しているだけです。
従って末尾から数えたい場合は、いちいち長さを調べてfoo[foo.length-1]
としなければなりませんでした。
今後は普通に書けるようになります。
Accessible Object.prototype.hasOwnProperty
hasOwnPropertyを使いやすくする提案。
let object = { foo: false };
Object.prototype.hasOwnProperty.call(object, "foo");
クソ長い!
ということでショートハンドです。
let object = { foo: false };
Object.hasOwn(object, "foo"); // true
ただのショートハンドなので、できることは同じです。
ところで素人目線だと、最初の例みたいな変なことをしないで
let object = { foo: false };
object.hasOwnProperty('foo');
って書けばいいのではと思ってしまうのですが、このような素直な例はプロトタイプ汚染によって死ぬので駄目なのです。
そのせいでObject.prototype.hasOwnProperty.call
なんて奇妙な書き方を強制され、そしてそれを短くするためにObject.hasOwn
なんてものが現れたと。
こんなことになったのも全てprototypeって奴の仕業なんだ。
Class Static Block
静的初期化ブロックです。
class Foo{
static {
console.log('static');
}
constructor(){
console.log('constructor');
}
}
static
ブロックは、クラスが定義された時点で実行されます。
Javaの静的初期化ブロックは初回インスタンス化のタイミングで評価されますが、こちらはさらに早く、クラスを読み込んだ時点で評価されます。
引数を渡すことはできません。
そんなタイミングもありませんしね。
ユースケースとしては、静的フィールドの初期値として評価式を入れたいときなど様々なものが考えられるでしょう。
class Foo{
static #privateValue;
static {
if(environment.production){
this.#privateValue = 1;
}else{
this.#privateValue = 2;
}
}
}
インスタンス化されていないので、静的でないインスタンスフィールドに値を入れたりはできません。
Error Cause
エラーが連鎖したときに、親を.cause
で辿れるようにします。
try{
try{
try{
throw new Error('Error1');
}catch(e){
throw new Error('Error2', {cause: e});
}
}catch(e){
throw new Error('Error3', {cause: e});
}
}catch(e){
console.table(e); // Error3
console.table(e.cause); // Error2
console.table(e.cause.cause); // Error1
}
第二引数にcause
を指定しなければなりません。
適当にhoge
とか渡しても持って行ってくれません。
エラーをキャッチして何かの付加情報を付けて再度エラーを出すみたいなことは時々やりたくなりますが、これまでは一般的な方法がなく、ライブラリやプロジェクトによって異なる書き方がされていました。
今後はこれに統一されることで、親を辿るのが楽になるでしょう。
感想
地味で地道だ。
いやなんというか、これまでのECMAScriptのProposalってどうも見た目の派手さばっかり求めて足元がおざなりって感じを受けていたのですが、今回ES2022に入ったのは、地味ながらも必要性が高く地道に使い勝手の高いものが多いという印象です。
個人の印象なので実際どうかは知りませんけどね。
新機能の多くはオブジェクトに関するものであり、これで他言語と遜色ないレベルでのクラス構文が書けるようになったと言っていいのではないでしょうか。
あとは早く;
必須にして。