🐥

ES2022の新機能についてまとめた

2024/04/22に公開

はじめに

先日ES2021までの新機能についてまとめた以下記事を公開しました。本記事ではES2022についてまとめています。
他記事を参考に、自分が見返したときに理解できるようにまとめた記事になりますので、もし不足事項等あればご指摘ください。
前回の記事はこちらです。
https://zenn.dev/kagunyan25/articles/0c3bd3d7ba2311

前回同様こちらの記事を参考にしています。
https://www.tohoho-web.com/ex/es2022.html#class-static-block

ES2022

トップレベル await

ES2022以前では、awaitはasync宣言した関数内でしか使うことができませんでした。
ES2022ではasync宣言なしでトップレベルにおいてawaitを使うことが可能です。
※モジュール内でのみ使用可能です。関数内ではawait宣言が必要です。

await getHogeData();

クラスフィールド宣言

ES2022では、コンストラクタ中で宣言する必要がなくなりました。

// before
class Counter {
  constructor() { // constructor内で定義が必要
    this.count = 0;
  }
  countUp() { this.count++; }
}
// after
class Counter {
  count = 0;
  countUp() { this.count++; }
}

プライベートフィールド・プライベートメソッド

ES2022以前、クラスフィールドはクラス外部からアクセスできてしまうパブリックなものしかありませんでした。
先頭に_をつけることで、プライベート扱いにしていました。

class Counter {
  constructor() { this._count = 0 }
  countUp() { this._count++; }
  count() { return this._count; }
}
var counter = new Counter();
counter.countUp();
console.log(counter.count()); // count()はプライベートではないのでアクセス可能
console.log(counter._count); // _countは先頭に_があるためプライベート扱いだが、アクセス可能

ES2022では、先頭に#をつけることで、プライベートフィールドであることを明確に宣言可能になりました。

class Counter {
  #count = 0;
  countUp() { this.#count++; }
  count() { return this.#count; }
}
var counter = new Counter();
counter.countUp();
console.log(counter.count()); // count()はプライベートではないのでアクセス可能
console.log(counter.#count); // #countはプライベートなのでアクセス不可→エラー

また、メソッドについても#をつけることでプライベート宣言が可能です。

class Counter {
  #count = 0;
  countUp() { this.#count++; }
  #count() { return this.#count; }
}

static イニシャライズブロック

staticフィールドの値を変更する場合、クラス定義の外に記述する必要がありました。

class Foo {
  static x = 123.4;
  static y;
}
Foo.y = Foo.x * 2;

ES2022以降では、staticイニシャライザを使うことでクラス定義内で記述可能になりました。

class Foo {
  static x = 123.4;
  static y;
  static {
    Foo.y = Foo.x * 2;
  }
}

こちらの記事を参考に理解しました。
https://www.bold.ne.jp/engineer-club/java-static-field#static

プライベートフィールドに対する in 演算子

あるオブジェクトがインスタンスであるかどうかはinstanceofを使い調べることが可能です。

class Foo { }
foo = new Foo();
console.log(foo instanceof Foo);

ただし、setPrototypeOfを使った場合、インスタンスではないのにinstanceofを使って調べた結果がtrueになってしまうことがありました。

class Foo { }
class Baa { }
foo = new Foo();
baa = new Baa();
Object.setPrototypeOf(baa, foo);
console.log(baa instanceof Foo); // true

Object.setPrototypeOf() メソッドは、指定されたオブジェクトのプロトタイプ (つまり、内部の [[Prototype]] プロパティ) を、別のオブジェクトまたは null に設定します。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf

instanceof 演算子は、あるコンストラクターの prototype プロパティが、あるオブジェクトのプロトタイプチェーンの中のどこかに現れるかどうかを検査します。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/instanceof

そのため、「プライベートフィールドである#brand にアクセスできるのは Foo のインスタンスのみである」ということを利用してインスタンスかどうかを調べる、ブランドチェックというテクニックが考案されました。

class Foo {
  #brand;
  static isFoo(obj) {
    try {
      obj.#brand;
      return true;
    } catch {
      return false;
    }
  }
}
class Baa { }
foo = new Foo();
baa = new Baa();
Object.setPrototypeOf(baa, foo);
console.log(Foo.isFoo(baa)); // false

これを簡略化したものがinを使う方法です。

class Foo {
  #brand;
  static isFoo(obj) {
    return #brand in obj;
  }
}
class Baa { }
foo = new Foo();
baa = new Baa();
Object.setPrototypeOf(baa, foo);
console.log(Foo.isFoo(baa)); // false

正規表現の d フラグによる開始・終了インデックス

正規表現に、マッチした部分文字列の開始・終了インデックスを得るためのdフラグが追加されました。
以下の場合、1つ目のindexには文字列全体の開始・終了インデックス、2つ目には1個目のマッチ文字列の開始・終了インデックスが入ります。

const result = "My name is Yamada".match(/My name is (.*)/d);
console.log(result); // ["My name is Yamada", "Yamada"]
console.log(result.indices); // [Array [0, 17], Array [11, 17]]

Error.cause によるエラーチェイン

なんらかの処理を持つfuncAとfuncBが存在し、funcXでは両方の処理を実行できるようにします。
このとき、以下のように記述可能ですが、例外が発生した場合に、funcAとfuncBどちら起因なのかわかりませんでした。

function funcX() {
  try {
    funcA();
    funcB();
  } catch (e) {
    throw new Error("FuncX() is failed.");
  }
}

ES2022では、causeパラメータを使用し、例外情報を得ることが可能になりました。

function funcX() {
  try {
    funcA();
    funcB();
  } catch (e) {
    throw new Error("FuncX() is failed.", { cause: e });
  }
}

try {
  funcX();
} catch (e) {
  console.log(e);        // FuncX() is failed.
  console.log(e.cause);  // funcB() is failed.
}

at(-n)で最後からN番目の要素を取得

ES2022ではatを使うことで、最後からN番目の要素を簡潔に取得可能になりました。

const foo = ["Red", "Green", "Blue"];

console.log(foo[foo.length - 1]);	// Blue
console.log(foo.at(-1)); // Blue

hasOwn() によるプロパティ保持チェック

オブジェクトが特定のプロパティを保持しているかを判断するとき、Object.prototype.hasOwnProperty.call(obj, prop)の代わりにObject.hasOwn(obj, prop)が使用できるようになりました。

Object.prototype を汚染するコードを記載していた場合や、hasOwnPropertyというメソッドがあった場合も簡潔にプロパティ保持チェックを行うことが可能です。

こちらについては以下の記事を読み、理解できましたので貼付しておきます。
https://www.tohoho-web.com/ex/es2022.html#hasown

まとめ

ES2021までに追加された機能に対し、ES2022の新機能は知らないものが多かったです。
また業務上ではあまり使っていないクラス周りの知識を得ることができてよかったです。
次回はES2023についてまとめたいと思います。

Discussion