🌼

正式仕様リリース! JavaScriptの最新仕様ES2022で追加された「全」新機能

鹿野 壮2022/06/22に公開4件のコメント

JavaScriptの仕様はECMAScriptで、ECMAScript 2015(ES2015)、ECMAScript 2016(ES2016)...というように毎年進化を続けています。

これまでの仕様はES2021でした。

https://zenn.dev/tonkotsuboy_com/articles/es2021-whats-new

本日6月22日、ES2022は正式仕様として承認され、ES2022が最新仕様となりました。

ブラウザ対応も完了しており、全モダンブラウザ(Google Chrome・Firefox・Safari・Microsoft Edge)でES2022の全機能が使えます。

本記事では、ES2022すべての新機能を紹介します。「何が使えるようになったのか?」「どうしてそれが必要だったのか?」が、できるだけわかりやすいように解説しました。

  • クラスフィールド宣言
  • プライベートなメンバー
  • instanceofの代わりのin
  • トップレベルでのawait
  • 配列の最後の要素を取得できるat()
  • hasOwnPropertyの代わりのObject.hasOwn()
  • staticイニシャライザー
  • エラーをチェインできる Error.cause
  • 正規表現のdフラグ

クラスフィールド宣言ができるようになった

ES2022では、これまでできなかったクラスフィールド宣言ができるようになりました。

class Human {
  age = 18;
  static category = "animal"
}

従来のクラスフィールド宣言

ES2015でJavaScriptにはクラス構文が導入されました。しかし、他の言語のクラス構文と異なり、JavaScriptではクラスフィールド宣言ができませんでした。フィールドを使いたいときは、次のように construtor()の中で宣言するしかありませんでした。

▼ 従来のフィールド

class Human {
  constructor() {
    this.age = 18;
  }
}

const human = new Human();
console.log(human.age); // 18

※ クラスメソッドはES2015から宣言可能です

ES2022ではクラスフィールド宣言できるように

ES2022からは、他言語と同じようにクラスフィールド宣言ができるようになりました。より直感的に、クラスの取り扱いができるようになります。

class Human {
  age = 18;
}

const human = new Human();
console.log(human.age); // 18

スタティックなクラスフィールド宣言

ES2022では、静的な(スタティックな)クラスフィールド宣言もできるようになりました。次のように記述すると、インスタンス化なしでクラスのフィールドにアクセスできます。

class Human {
  static category = "animal"
}

console.log(Human.category);  // animal

関連資料

プライベートなフィールドとメソッドが使えるようになった

プライベートなフィールドやメソッドとは、クラスの外からアクセスできず、クラスの内側からしかアクセスできないものです。

class MyClass {
  #foo = "ラーメン";
  
  #bar() {
    console.log("うどん")
  }
}

従来のフィールドやメソッドはすべてパブリックだった

ES2022以前では、クラス構文で作ったフィールドやメソッドは、すべて「パブリック」でした。「パブリック」とは、クラスの内部、外部のどちらからでもアクセスできることを指します。

次の例では、コンストラクターに渡したnamenameフィールドに保持し、hello()メソッドで出力するプログラムです。次の箇所で、nameフィールドにアクセスできていていることがわかります。

  • クラスの内部(hello()メソッド内部)
  • クラスの外側(foo.nameの箇所)

▼ パブリックなフィールドの挙動の確認

class MyClass {
  name;
  
  constructor(name) {
    this.name = name
  }

  hello() {
    console.log(`こんにちは${this.name}さん!`)
  }
}

const foo = new MyClass("田中");
foo.hello(); // 「こんにちは田中さん!」と出力される
console.log(foo.name);  // 「田中」と出力される
// nameの書き換えが可能
foo.name = "鈴木";
foo.hello(); // 「こんにちは鈴木さん!」と出力される

一度「田中」の文字列を渡したら、クラスの外から書き換えたくない場合、上記のようなパブリックなフィールドでは不適切になります。

#を使ってプライベートなフィールドを宣言する

#を使うと、「プライベートな」プロパティを宣言できます。「プライベート」とは、クラスの内部からしかアクセスできないことを指します。クラスの外部からアクセスしようとするとエラーになります。

▼ プライベートなフィールドの確認

class MyClass {
  // プライベートなフィールド
  #name;
  
  constructor(name) {
    this.#name = name
  }

  hello() {
    console.log(`こんにちは${this.#name}さん!`)
  }
}

const foo = new MyClass("田中");
foo.hello(); // 「こんにちは田中さん!」と出力される

上記の処理のあとに、次のコードを記述するとエラーになります。プライベートなフィールドなので、クラスの外から読み取れないのです。

console.log(foo.#name);  // エラー

▼ エラーが発生している様子

もちろん、読み取れないので、書き換えることも不可能です。

ちなみに、なぜ _でもprivateでもなく#になったのは、次のドキュメントにまとまっていますので、興味があればご覧くださいませ。

メソッドやスタティックとも組み合わせられる

プライベートなメソッド、プライベートでスタティックなフィールド・メソッドも作ることができます。前述のクラスフィールド宣言とあわせ、ES2022からは次のフィールド・メソッド宣言ができるようになりました。

  • パブリックフィールド
  • プライベートフィールド
  • パブリック・スタティック・フィールド
  • プライベート・スタティック・フィールド
  • パブリック・メソッド
  • プライベート・メソッド
  • パブリック・スタティック・メソッド
  • プライベート・スタティック・メソッド

すべてを試せるデモがこちらです。

class MyClass {
  // public field
  foo = "public field: 寿司";
  // private field
  #bar = "private field: ラーメン";

  // public static field
  static qux = "public static field: うどん";
  // private static field
  static #corge = "private static field: 麻婆豆腐";

  // public method
  grault() {
    return console.log("public method: みかん");
  }

  // private method
  #garply() {
    return console.log("private method: ぶどう");
  }

  // public static method
  static waldo() {
    return console.log("public static method: みかん");
  }

  // private static method
  static #fred() {
    return console.log("private static method: ぶどう");
  }
}

コラム: TypeScriptのprivate修飾子との違い

TypeScriptでは、プライベートなメンバーを宣言できるprivate修飾子があります。ES2022の#との違いは、ラインタイム時にチェックされるかどうかです。TypeScriptの場合、privateでメンバーを宣言したとしても、コンパイル後のJavaScriptではパブリックなメンバーとなります。

▼ TypeScriptのprivateとES2022の#

筆者的には、コンパイル時・ランタイム時にチェックできるES2022の#を好みます。

関連資料

instanceofよりも安全にインスタンスかどうかの確認ができる、プライベートフィールドのin演算子

プライベートフィールドに対してin演算子が使えるようになりました。instanceofよりも安全にインスタンスかどうかの確認ができるようになります。提案では「Ergonomic brand checks」と呼ばれています。

▼ 簡単な例

class MyClass {
  #brand
  static isMyClass(object) {
    return #brand in object;
  }
}

console.log(MyClass.isMyClass(new MyClass())); // true
console.log(MyClass.isMyClass(new Date())); // false

前提知識① insntanceof演算子は正確にインスタンスのチェックができない

あるインスタンスが、あるクラスのインスタンスかどうかを調べるかどうかを調べるために、instanceofメソッドが使われているケースをよく見かけます。

class MyClass {
}

const myInstance = new MyClass();
console.log(myInstance instanceof MyClass); // true

しかし、instanceofには注意すべき挙動があります。次のコードを見てください。

class MyClass {
}

const myInstance = new MyClass();

const foo = {
  name: "名探偵コナン"
};

// ポイント
Object.setPrototypeOf(foo, myInstance);
console.log(foo instanceof MyClass); // trueになってしまう!

fooは、MyClassのインスタンスではありません。しかし、オブジェクトのプロトタイプを書き換えるObject.setPrototypeOfを用いると、foo instanceof MyClasstrueになってしまうのです。

instanceofはプロトタイプベースであり、正確に「インスタンスかどうか」をチェックできるものではないのです。

前提知識② プライベートフィールドでインスタンスのチェック

上記の問題を解決するために、プライベートフィールドを使います。プライベートフィールドには、存在しないプライベートフィールドにアクセスしようとすると例外をスローをする仕組みがあります。

class MyClass {
  #myBrand
  
  static check(object) {
    object.#myBrand;
  }
}

const myInstance = new MyClass();
MyClass.check(myInstance); // OK

const foo = {
  name: "名探偵コナン"
};
MyClass.check(foo); //例外をスロー

この仕組みを使うと、instanceofよりも正確にインスタンスかどうかの確認ができます。

MyClassにスタティックなメソッドisMyClassを定義して、引数のオブジェクトがMyClassのインスタンスかどうかをチェックできるようにしてみましょう。trycatchを使い、エラーが起こらない(=存在するプライベートフィールドへのアクセス)であればtrueを返すようにします。

class MyClass {
  #myBrand

  static isMyClass(object) {
    try {
      object.#myBrand;
      return true;
    } catch {
      return false
    }
  }
}

const myInstance = new MyClass();
console.log(MyClass.isMyClass(myInstance)); // true

const foo = {
  name: "名探偵コナン"
};

// ポイント
Object.setPrototypeOf(foo, myInstance);
console.log(MyClass.isMyClass(foo)); // false

正しくインスタンスのチェックができるようになりました。が、trycatchの処理は煩雑ですね。そこで登場したのが、ES2022のプライベートフィールドのためのinです。

inでシンプルにインスタンスかどうかの確認

inを使えば、わざわざ trycatchを使わず、シンプルにインスタンスかどうかのチェックができます。

class MyClass {
  #myBrand

  static isMyClass(object) {
    return #myBrand in object;
  }
}

const myInstance = new MyClass();
console.log(MyClass.isMyClass(myInstance)); // true

const foo = {
  name: "名探偵コナン"
};
console.log(MyClass.isMyClass(foo)); // false

Object.setPrototypeOf(foo, myInstance);fooのプロトタイプを書きかえたとしても、正しく動作します。

Object.setPrototypeOf(foo, myInstance);
console.log(MyClass.isMyClass(foo)); // falseのまま

デモは次のとおりです。

コラム: tc39の「ブランドチェック」

あるコードで作られたデータが、特定のデータ型かどうかをチェックすることを、tc39では「ブランドチェック(brand check)」と呼んでいます。Array.isArray(データ)で引数のデータが配列かどうかをチェックすることは、ブランドチェックの好例です。よく間違えられていますが、instanceofはブランドチェックではありません

今回のようにあるオブジェクトがMyClassのデータかどうかをチェックすることは、プライベートフィールドとin演算子を使ってブランドチェックをしていると言えます。inを使ってシンプルにブランドチェックができるので、この提案の名前は「Ergonomic brand checks」(使いやすいブランドチェック)なのです。

関連資料

asyncなしでもawaitが使えるようになる、トップレベルでのawait

トップレベルでのawaitとは、asyncを使わずともawaitが使えるようになる構文のことです。

▼ 簡単な例

await new Promise((resolve) => {
  setTimeout(() => {
    alert("1秒経ちました");
    resolve();
  }, 1000);
});

従来はasync関数の中でしかawaitを使えなかった

await を使うと、非同期処理を簡潔に記述できます。しかし、これまで await は、 async 関数の中でしか使えず、トップレベルで使うことはできませんでした。

▼ ES2021以前の await

const main = async () => {
  await new Promise((resolve) => {
    setTimeout(() => {
      alert("ヨーソロー!");
      resolve();
    }, 1000);
  });
}

main();

ES2022からは async 関数外でもawaitを使える

ES2022からは、async 関数の外でも awaitを使えます。

▼ ES2022以降の await

await new Promise((resolve) => {
  setTimeout(() => {
    alert("ヨーソロー!");
    resolve();
  }, 1000);
});

トップレベルでの await はモジュールでのみ使用可能

トップレベルでの awaitは、モジュールでのみ使用可能です。
scriptタグでJavaScriptを読み込む際に type="module"とすることで、JavaScriptがモジュールで実行されます。

▼ HTMLでJavaScriptの読み込み

<script type="module" src="index.js"></script>

ユースケース:i18n用にファイルを読み込む

トップレベルawaitの活用例として、i18n対応(国際化対応)の方法を見てみましょう。次のように、タイトルとボタンを表示するHTMLがあるとします。タイトルとボタンのラベルは、ブラウザUIが英語設定であれば英語を、日本語設定であれば日本語を表示したいです。

<h1></h1>
<button></button>

日本語用・英語用のテキストは、次の2つのJavaScriptファイルにまとまっており、ユーザーの環境に応じてどちらか1つのファイルを読み込みたいです。

▼ 日本語用のlang-ja.js

export const translations = {
  title: "私のウェブサイト",
  button: "ボタン"
};

▼ 英語用のlang-en.js

export const translations = {
  title: "My Website",
  button: "Button"
};

トップレベルawaitを使えば次のようにできます。ブラウザーUIの言語設定(navigator.language)に英語が含まれていれば lang-en.jsを、そうでなければ lang-ja.jsを読み込みます。言語設定が英語かどうかを/^en\b/.test(navigator.language)で判定する理由は、 @yasshさんのコメントがわかりやすいです(コメントありがとうございます!)。

読み込んだ結果を、さらに exportします。

▼ lang.js

export const { translations } =
  /^en\b/.test(navigator.language) ?
    await import("./i18n/lang-en.js"):
    await import("./i18n/lang-ja.js");

メインの処理index.jsでは、ファイルの読み分けを行っているlang.jsを静的なimportimport()ではない)で読み込めます。lang.jsでのファイル読み込みが完了するまでindex.js内の処理は遅延され、translations には必ずデータが入った状態でタイトルやテキストの書き換えが行われます。このあたりの話は、「top-level awaitがどのようにES Modulesに影響するのか完全に理解する - Qiita」(著者: @uhyo_)でわかりやすく解説されています。

▼ index.js

import { translations } from "./lang.js";

document.querySelector("h1").textContent = translations.title;
document.querySelector("button").textContent = translations.button;

実行結果は次のとおりです。Google Chromeでは、言語設定(chrome://settings/languages)から言語設定を変更できます。日本語設定の場合、日本語のタイトル・ボタンラベルが表示されます。

英語設定に変更すると、英語のタイトル・ボタンラベルが表示されます。

もしトップレベルawaitがなかったら、同様の処理をしたければ複雑なコードを記述する必要があります(参考)。トップレベルawaitのおかげでスッキリとしたコードが書けるのです。

デモは次のとおりです。

コラム: tc39のサンプルコードは注意

念のためにお伝えしておくと、tc39のREADMEに記述されている次のコードは、実用にはオススメできません。

const strings = await import(`/i18n/${navigator.language}`);

理由は2つ。

  • navigator.languageの数だけファイルを準備する必要があります。英語だけでも、enen-USのファイルを作る必要があります
  • stringsの型がany型にしか推論されなくなります。navigator.languageを直接使っているので、ファイル名が一意に定まりません。記事内のlang.jsの記法では、ファイル名は lang-en.jslang-ja.jsのどちらかに定まります。よって、translations(tc39でいうstrings変数)はtitlebuttonがそれぞれstringのオブジェクトと推論されます。

▼ 型が推論される様子(JavaScript・TypeScriptともに)

あくまで説明用の部分的なコードであると注意しましょう。

関連資料

配列の「最後の要素」が簡単に取得できるようになるat()

配列のat()メソッドを使うと、インデックスを指定して要素を取得できます。

構文

配列.at(インデックス)

■ 意味
配列インデックス位置の要素を取得してください

文字列.at()もES2022から使える機能です

従来は、配列の最後の要素を取得するのは煩雑だった

「配列の最後の要素を取得したい」というケースはよくあります。配列は[]でインデックスを指定して要素を取得できますが、-1と指定したからといって最後の要素は取得できません(できる言語もあります)。

よって、配列の最後の要素を取得するには、次のような煩雑な処理が行われていました。myArray.lengthが配列の要素数を返すことを利用し、myArray.length - 1で配列の最後の要素のインデックスを指定していたのです。

const myArray = ["りんご", "バナナ", "ぶどう"];
console.log(myArray[myArray.length - 1]); // ぶどう

at(-1)で最後の要素を取得できる

配列のat()メソッドを使うと、位置を指定して要素を取得できます。本メソッドの嬉しいところは、配列の最後の要素の取得が簡単になることです。前述の処理よりも直感的ですね。

const myArray = ["りんご", "バナナ", "ぶどう"];
console.log(myArray.at(-1)); // ぶどう

デモはつぎのとおりです。

関連資料

オブジェクトが指定のプロパティを持っているかを簡単にチェックできる Object.hasOwn()

Object.hasOwn()を使うと、オブジェクトが指定のプロパティを持っているかを簡単かつ安全にチェックできます。

構文

Object.hasOwn(オブジェクト, プロパティ);

■ 意味
オブジェクトが指定のプロパティを持っているかどうか?を真偽値で返す

■ 戻り値
真偽値

従来はhasOwnProperty()で面倒な記述が必要だった

従来、同様の判定をするメソッドとして hasOwnProperty() メソッドがありました。しかし、安易にhasOwnProperty()を使うのは危険です。対象のオブジェクトが同名のhasOwnPropertyという名前のメソッドを持っていた場合、上書きされてしまうためです。

▼ 従来のhasOwnProperty()の危険性

const myObject = {
  name: "鈴木",
  hasOwnProperty: () => {
    // hasOwnPropertyが上書きされる
    return false;
  },
}

console.log(myObject.hasOwnProperty("name"));
// nameはあるのに常にfalse

この挙動を回避するためには、次のようにプロトタイプのhasOwnPropertyプロパティを使う必要があります。

Object.prototype.hasOwnProperty.call(myObject, 'name'); // true

ちなみに、ESLintを使っていれば、myObject.hasOwnProperty("name")のような処理はデフォルトで警告されます。

▼ ESLintでのエラー(確認コード

ES2022からはhasOwn()でシンプルに記述できる

ES2022で実装されたhasOwn()メソッドを使えば、前述のようなプロトタイプのプロパティを使うことなく、シンプルにプロパティの確認ができます。

const myObject = {
  name: "鈴木",
}

console.log(Object.hasOwn(myObject, "name"));
// true

hasOwnPropertyを上書きしたとしても、正しくチェックできます。

const myObject = {
  name: "鈴木",
  hasOwnProperty: () => {
    return false;
  },
}

console.log(Object.hasOwn(myObject, "name"));
// true

デモは次のとおりです。

関連資料

JavaScriptで「staticイニシャライザー」ができるように

staticイニシャライザーとは、クラス定義時に初期化処理を一度だけ実行できるブロックのことです。Javaなどの世界ではすでにある仕組みです。

▼ 簡単な例

class MyClass {
  static x;
  
  static {
    this.x = "こんにちは"
  }
}

console.log(MyClass.x); // こんにちは

staticイニシャライザーがなぜ必要か?

ひょんなことから、enum(列挙型)を作る必要が出てきたとしましょう。列挙型とは、似た値を集めたものです。たとえば、果物用の列挙型を作成し、FruitsEnum.appleFruitsEnum.grapeのような形で扱えるようにしてみましょう。

次のような FruitsEnum の実装が考えられます。

class FruitsEnum {
  static apple = Symbol("りんご");
  static orange = Symbol("みかん");
  static grape = Symbol("ぶどう");
}

FruitsEnumから、すべてのキーを取得できるスタティックなフィールドallFruitsをクラス内に実装したいとします。

staticイニシャライザーが使えない時代は、次のようにクラス外に処理を記述する必要がありました。

class FruitsEnum {
  static apple = Symbol("りんご");
  static orange = Symbol("みかん");
  static grape = Symbol("ぶどう");

  static allFruits;
}

FruitsEnum.allFruits = Object
  .keys(FruitsEnum)  // 各キーを取得する
  .filter(key => key !== "allFruits");  // allFruitsを除外する

console.log(FruitsEnum.allFruits);
// [apple, orange, grape]

staticイニシャライザーを使えば、クラス内に処理を記述できる

staticイニシャライザーを使えば、次のようにクラス内に処理を記述できます。クラスの外部に処理を記述しなくてよくなるので、処理のまとまりがわかりやすくなります。

class FruitsEnum {
  static apple = Symbol("りんご");
  static orange = Symbol("みかん");
  static grape = Symbol("ぶどう");

  static {
    this.allFruits = Object.keys(this);
  }
}

console.log(FruitsEnum.allFruits);
// [apple, orange, grape]

デモは次のとおりです。

関連資料

複数のエラーをチェインし、原因を追跡しやすくできるError.cause

Error.causeとは、エラーを投げるときcauseプロパティにエラーオブジェクトを保持できるものです。エラーのチェインに便利です。

構文

try {
  // なにかしらのエラーが発生する
} catch(error) {
  throw new Error("エラーの内容", { cause: error })
}

■ 意味
エラーを投げるとき、エラーのcauseプロパティにerrorを格納してください

複数のエラーを投げるときの問題点

「API1の通信に成功したら、API2と通信したい。それぞれどこで失敗したかに応じて、処理を振り分けたい」というように、複数のエラーを取り扱いたいケースは多くあります。今回は、50%の確率でエラーになる関数を使って確認してみましょう。

次の恐ろしく危険な2つの関数があります。それぞれ、50%の確率でオブジェクトが定義されていないというエラー(ReferenceError)が投げられます。

▼ 恐ろしく危険な2つの関数

const function1 = () => {
  if (Math.random() > 0.5) {
    // fooが定義されていないというReferenceError
    foo.bar;
  }
};

const function2 = () => {
  if (Math.random() > 0.5) {
    // bazが定義されていないというReferenceError
    baz.qux;
  }
};

function1();
function2();

それぞれの関数内で、trycatchを使って例外をキャッチしましょう。このとき、catchの中身をどうするのかにはいくつかのアプローチがあります。

// 50%の確率でエラー
const function1 = () => {
  try {
    if (Math.random() > 0.5) {
      foo.bar;
    }
  } catch (error) {
    // どうする?
  }
};

// 50%の確率でエラー
const function2 = () => {
  try {
    if (Math.random() > 0.5) {
      baz.qux;
    }
  } catch (error) {
    // どうする?
  }
};

// function1とfunction2を実行する
try {
  function1();
  function2();
  console.log("成功です!");
} catch (error) {
  console.log(error);
  console.log(error.cause);
}

方法1: throw new Error("文字列")

catch(error) {
  throw new Error("fooプロパティなんてないよ😡");
}

この場合、ReferenceErrorの情報は消えてしまいます。

方法2: Errorを拡張したカスタムクラスを作成する

class CustomError extends Error {
  constructor(message, cause) {
    super(message);
    this.cause = cause;
  }
}

// 中略
catch(error) {
  throw new CustomError("fooプロパティなんてないよ😡", error);
}

この場合は、CustomErrorを作るのが煩雑です。

Error.cause を使ってエラーをチェインさせる

causeプロパティを使うと、元のエラーの情報を残しつつ、エラーを投げることができます。

// 50%の確率でエラー
const function1 = () => {
  try {
    if (Math.random() > 0.5) {
      foo.bar;
    }
  } catch (error) {
    throw new Error("fooが存在しないですよ😡", {
      cause: error
    });
  }
};

// 50%の確率でエラー
const function2 = () => {
  try {
    if (Math.random() > 0.5) {
      baz.qux;
    }
  } catch (error) {
    throw new Error("bazが存在しないですよ🤯", {
      cause: error
    });
  }
};

// function1とfunction2を実行する
try {
  function1();
  function2();
  console.log("成功です!");
} catch (error) {
  console.log(error);
  console.log(error.cause);
}

function1function2でエラーが発生すると、それぞれ次のような出力になります。いずれも、エラーメッセージと、error.causeから元エラー(ReferenceError)の情報が取得できているのがわかります。

function1でエラー

function2でエラー

デモは次のとおりです。

関連資料

正規表現で、マッチ部分の開始・終了インデックスを取得できるdフラグ

dフラグとは、正規表現でマッチ部分の開始・終了インデックスを取得できるものです。

構文

const result = /正規表現/d.exec(文字列);
console.log(result.indices);

■ 意味
文字列から、正規表現にマッチする部分を取得してください。
取得したら、indices プロパティにマッチ部分の開始・終了インデックスを格納してください。

前提知識① 正規表現でマッチ部分の情報を取得する

/正規表現/.exec(文字列)や、文字列.match(正規表現)を実行すると、正規表現にマッチした文字列の情報を取得できます。たとえば、次のコードは/私の姓は(.*)、名前は(.*)です/という正規表現にマッチする文字列の情報を取得しています。

// 正規表現
const regrex = /私の姓は(.*)、名前は(.*)です/;

const result = '私の姓は山田、名前は太郎です'.match(regrex);
console.table(result);

実行結果は次の通り。「山田」と「太郎」という文字列が取得できているのがわかります。

前提知識② フラグとマッチ部分に名前をつける

ES2018では、Named Capture Groupsという仕様が入りました。正規表現で?<グループ名>とすることで、マッチした部分にグループ名をつけることができ、マッチ部分の情報を探しやすくなります。なお、この場合はuフラグが必要です

const regrex = /私の姓は(?<family>.*)、名前は(?<name>.*)です/u;

const result = '私の姓は山田、名前は太郎です'.match(regrex);
console.table(result);

console.log(result.groups.family);  // 山田
console.log(result.groups.name);  // 太郎

実行結果は次のとおりです。

d フラグでマッチ部分の開始・終了インデックスを取得する

本題のdフラグです。dフラグを使うと、マッチ部分の開始・終了インデックスを取得できます。前述のNamed Capture Groupsとあわせて使うことで、マッチ部分にグループ名をつけつつ、簡単に開始・終了インデックスを取得できるようになります。

const regrex = /私の姓は(?<family>.*)、名前は(?<name>.*)です/du;

const result = '私の姓は山田、名前は太郎です'.match(regrex);
console.log(result);

// ☆ indicesプロパティでマッチ部分の開始・終了インデックスを取得する
const indicesGroups = result.indices.groups;
console.log(indicesGroups.family);  // [4, 6]
console.log(indicesGroups.name);  // [10, 12]

「☆ indicesプロパティでマッチ部分の開始・終了インデックスを取得する」のところでは、それぞれ[4, 6][10, 12]という配列が取得されています。次のことを示します。

  • 「山田」という文字列の開始インデックスは4、終了インデックスは6
  • 「太郎」という文字列の開始インデックスは10、終了インデックスは12

デモは次のとおりです。

関連資料

対応環境

本記事で紹介したES2022の全機能は、各環境で使用可能です。

  • Google Chrome
  • Firefox
  • Safari
  • Microsoft Edge
  • Node.js

ECMAScript compatibility table」の「2022 features」で、細かい対応バージョンを確認できます。

SafariのES2022対応が遅れていましたが、今年リリースされた15.4ですべて対応しました。

ES2022を使って便利に開発しよう

本記事では正式仕様としてリリースされたES2022の新機能を紹介しました。どれも開発をラクにしてくれるものばかりで、筆者も積極的に開発の現場で使っています。

ECMAScriptは次のES2023に向けて仕様策定がすでに始まっています。findLast()findLastIndex()など、また便利な機能が入ってきそうです。新しい機能をキャッチアップし、楽しく開発していきましょう。

ES2022のLanguage Specificationは、こちらから確認できます。

TwitterでもJavaScriptやCSSの最新情報を発信しています。ES2023以降の情報や、ステージ3以下の便利そうなJavaScriptなども紹介します。

https://twitter.com/tonkotsuboy_com
GitHubで編集を提案

Discussion

ES2022とは関係のない点を指摘することをお許しください。

export const { translations } =
  navigator.language.includes("en") ?
    await import("./i18n/lang-en.js"):
    await import("./i18n/lang-ja.js");

この例は、navigator.language.includes("en")の部分があまりよくないと思います。

仕様によると、navigator.languageBCP 47の言語タグを返すとされています。そしてBCP 47の言語タグは、次のような形式らしいです。

 langtag       = language
                 ["-" script]
                 ["-" region]
                 *("-" variant)
                 *("-" extension)
                 ["-" privateuse]

navigator.languageの返り値のうち、languageの部分が"en"ではなくても、script、region、variantなどに"en"が含まれていた場合、navigator.language.includes("en")trueを返してしまうでしょう。

この問題を回避するために、navigator.language.includes("en")ではなく/^en\b/.test(navigator.language)と書くほうがよいと思います。(MDNのNavigator.languageのページの例も、そのように書かれています。)

ご指摘のとおりでしたので更新いたしました!
細かい部分までお読みいただき、ありがとうございます!

素晴らしいまとめ記事をありがとうございます!

以下、1点念のため確認したいです。


Error Causeの部分で以下2つのコードが例示されています。

.catch(error) {
  throw new Error("fooプロパティなんてないよ😡");
}
.catch(error) {
  throw new CustomError("fooプロパティなんてないよ😡", error);
}

これらは、try-catchブロックの // どうする? の部分に相当するコードだと認識しているのですが、その場合はPromise Chainの catch メソッドではなくて } catch (error) { ... } ではないでしょうか?

try {
  if (Math.random() > 0.5) {
    baz.qux;
  }
} catch (error) {
  // どうする?
}

ありがとうございます!
ご指摘のとおりでしたので修正しました🙏

ログインするとコメントできます