【JavaScript】ES2022の新機能

[ES2023](https://qiita.com/rana_kualu/items/84f66fe970f7feccf367) / [ES2022](https://qiita.com/rana_kualu/items/8bafecd760ae69cfac41) / [ES2021](https://qiita.com/rana_kualu/items/ae3297dd2974fcf047c4)


もうじき2022年ということで、[ES2022にFinished Proposalsとして取り入れられた機能](https://github.com/tc39/proposals/blob/master/finished-proposals.md)がそれぞれどんなものなのか見てみることにします。

ちなみにFinished Proposalの手前の段階であるStage4が「複数の実装が既に存在する」というのが定義なので、2022という名前にもかかわらず既に一部のブラウザで使用可能、というか一部以外のブラウザで使用可能です。


# ES2022

## Class Fields

Class Fieldsは似たような3RFCの集合体です。

### [Private instance methods and accessors](https://github.com/tc39/proposal-private-methods)

privateメソッドです。
むしろ今まで存在してなかったことにびっくりだよ。

```js
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です。

あとアクセサにも付けられるようになりますが、こちらは正直用途がよくわかりません。

```js
class Foo{
  get #x(){return this.value;}
  set #x(value){this.value = value;}
}
```

### [Class Public Instance Fields & Private Instance Fields](https://github.com/tc39/proposal-class-fields)

public・privateフィールドです。
むしろ今まで存在してなかったことにびっくりだよ。

```js
class Foo{
  x = 1;
  #y = 1;
}
```

publicについては、JSではいつでもどこでも`foo.x = 1`とかpublicフィールドを生やせるので究極的には要らないと言えば要らないのですが、まあ明示できた方がいいですよね。

一方でprivateフィールドは、いくら明示したとしても外からアクセスすることはできません。

ところでこの話が出てきたのって[だいぶ前だった](https://qiita.com/tsuuuuu_san/items/025a398ca284d4b368bf)気がするのですが、Finishedになったのって今年になってようやくだったんですね。

### [Static class fields and private static methods](https://github.com/tc39/proposal-static-class-features)

静的フィールド・メソッドです。
むしろ今まで存在してなかったことにびっくりだよ。

```js
class Foo{
  static x = 1;
  static #y = 2;
  
  static bar(){}
  static #baz(){}
}
```

上で出てきたばかりのprivateフィールドやprivateメソッドにも対応します。

このあたりの機能が出揃ったことによって、素のJavaScriptでもようやくまともなクラス構文が書けるようになりそうですね。


## [RegExp Match Indices](https://github.com/tc39/proposal-regexp-match-indices)

正規表現マッチングに、マッチした位置の情報を追加します。

```js
const result = /ef(gh)(i)j/d.exec('abcdefghijklmn');
console.table(result.indices);
```

![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/dc5c0333-73e7-25b7-c72a-d86a288f3f18.png)

正規表現にフラグ`d`を追加すると、返り値に`indices`が増えます。
追加が必要なのはパフォーマンス的な理由であり、フラグがない場合はこれまでと同じ挙動になります。

`indices`は配列となっていて、0には正規表現全体が文字列にマッチした部分が入ってきます。
例であれば4-10となっていますが、この返り値は`slice`でちょうど切り取れる値です。

```js
'abcdefghijklmn'.slice(4, 10); // 'efghij'
```

1以降はキャプチャグループがあればそのマッチした範囲が入ります。
例であれば1に`(gh)`、2に`(i)`のキャプチャグループの結果が入っています。

今回の例では実質的に正規表現を使ってないのであまり意味がないですが、キャプチャ文字列長が不定になるような正規表現を使うときなどは特に便利になることでしょう。


## [Top-level await](https://github.com/tc39/proposal-top-level-await)

async関数の外側でawaitを使えるようになります。

```js
await 1;
```

これができて何が便利かというと、さくっとexportしたりできます。

```js
export const hoge = await f();
```

もっと詳しい記事が[こちらにあった](https://qiita.com/uhyo/items/0e2e9eaa30ec2ff05260)ので、詳細はここなどを見るとよいと思います。
何言ってるのか9割方わかりませんでした。


```js:だめ
function f(){
  await 1; // Uncaught SyntaxError: await is only valid in async functions
}
```

なお、asyncでない関数の中では相変わらず使用できません。
なんで。

## [Ergonomic brand checks for Private Fields](https://github.com/tc39/proposal-private-fields-in-in)

`人間工学に基づいたprivateフィールドのチェック`
なんのこっちゃ。

`in`をprivateフィールドにも使用可能にします。

```js
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フィールドは外から見えないので、自身のクラス内でしか使用できません。

つまり、これとだいたい同じです。

```js
class Foo{
  #privateField = 1;
  
  static isFoo(obj){
    return obj instanceof Foo;
  }
}
```

[細かいところで動作が異なる](https://cybozu.github.io/frontend-expert/posts/ergonomic-brand-checks-for-private-fields)らしいのですが、そこまでしてこの機能を必要とする理由はよくわかりませんでした。


## [.at](https://github.com/tc39/proposal-relative-indexing-method)

全てのインデックス可能なクラス`Array・String・TypedArray`に、指定位置の値を取得するメソッド`.at()`を追加します。

```js
'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'`みたいに範囲を取得することはできません。

一見`[]`と同じやん、と思いますが、実は`[]`は負の値を使うことができません。

```js
'abcdef'[0]; // a
'abcdef'[1]; // b
'abcdef'[-1]; // undefined
```

というより、`[]`は単にオブジェクトのプロパティにアクセスしているだけであって、たとえば`foo[-1]`は`foo`オブジェクトのプロパティ`-1`を参照しているだけです。
従って末尾から数えたい場合は、いちいち長さを調べて`foo[foo.length-1]`としなければなりませんでした。
今後は普通に書けるようになります。


## [Accessible Object.prototype.hasOwnProperty](https://github.com/tc39/proposal-accessible-object-hasownproperty)

hasOwnPropertyを使いやすくする提案。

```js
let object = { foo: false };
Object.prototype.hasOwnProperty.call(object, "foo");
```

クソ長い！

ということでショートハンドです。

```js
let object = { foo: false };
Object.hasOwn(object, "foo"); // true
```

ただのショートハンドなので、できることは同じです。


ところで素人目線だと、最初の例みたいな変なことをしないで

```js
let object = { foo: false };
object.hasOwnProperty('foo');
```

って書けばいいのではと思ってしまうのですが、このような素直な例は[プロトタイプ汚染](https://sosukesuzuki.dev/posts/stage-3-object-hasown/)によって死ぬので駄目なのです。
そのせいで`Object.prototype.hasOwnProperty.call`なんて奇妙な書き方を強制され、そしてそれを短くするために`Object.hasOwn`なんてものが現れたと。
こんなことになったのも全てprototypeって奴の仕業なんだ。


## [Class Static Block](https://github.com/tc39/proposal-class-static-block)

静的初期化ブロックです。

```js
class Foo{
  static {
    console.log('static');
  }
  
  constructor(){
    console.log('constructor');
  }
}
```

`static`ブロックは、クラスが定義された時点で実行されます。
[Javaの静的初期化ブロック](https://docs.oracle.com/javase/specs/jls/se10/html/jls-8.html#jls-8.7)は初回インスタンス化のタイミングで評価されますが、こちらはさらに早く、クラスを読み込んだ時点で評価されます。

引数を渡すことはできません。
そんなタイミングもありませんしね。

ユースケースとしては、静的フィールドの初期値として評価式を入れたいときなど様々なものが考えられるでしょう。

```js
class Foo{
  static #privateValue;
  
  static {
    if(environment.production){
      this.#privateValue = 1;
    }else{
      this.#privateValue = 2;
    }
  }
}
```

インスタンス化されていないので、静的でないインスタンスフィールドに値を入れたりはできません。

## [Error Cause](https://github.com/tc39/proposal-error-cause)

エラーが連鎖したときに、親を`.cause`で辿れるようにします。

```js
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に入ったのは、地味ながらも必要性が高く地道に使い勝手の高いものが多いという印象です。
個人の印象なので実際どうかは知りませんけどね。

新機能の多くはオブジェクトに関するものであり、これで他言語と遜色ないレベルでのクラス構文が書けるようになったと言っていいのではないでしょうか。


あとは早く`;`必須にして。