正式仕様リリース! JavaScriptの最新仕様ES2022で追加された「全」新機能
JavaScriptの仕様はECMAScriptで、ECMAScript 2015(ES2015)、ECMAScript 2016(ES2016)...というように毎年進化を続けています。
これまでの仕様はES2021でした。
本日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以前では、クラス構文で作ったフィールドやメソッドは、すべて「パブリック」でした。「パブリック」とは、クラスの内部、外部のどちらからでもアクセスできることを指します。
次の例では、コンストラクターに渡したname
をname
フィールドに保持し、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: ぶどう");
}
}
private
修飾子との違い
コラム: TypeScriptの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 MyClass
がtrue
になってしまうのです。
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
のインスタンスかどうかをチェックできるようにしてみましょう。try
・catch
を使い、エラーが起こらない(=存在するプライベートフィールドへのアクセス)であれば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
正しくインスタンスのチェックができるようになりました。が、try
・catch
の処理は煩雑ですね。そこで登場したのが、ES2022のプライベートフィールドのためのin
です。
in
でシンプルにインスタンスかどうかの確認
in
を使えば、わざわざ try
・catch
を使わず、シンプルにインスタンスかどうかのチェックができます。
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();
async
関数外でもawait
を使える
ES2022からは 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
を静的なimport
(import()
ではない)で読み込めます。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
の数だけファイルを準備する必要があります。英語だけでも、en
やen-US
のファイルを作る必要があります -
strings
の型がany
型にしか推論されなくなります。navigator.language
を直接使っているので、ファイル名が一意に定まりません。記事内のlang.js
の記法では、ファイル名はlang-en.js
かlang-ja.js
のどちらかに定まります。よって、translations
(tc39でいうstrings
変数)はtitle
とbutton
がそれぞれ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でのエラー(確認コード)
hasOwn()
でシンプルに記述できる
ES2022からは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
デモは次のとおりです。
関連資料
- Accessible Object.prototype.hasOwnProperty() - tc39
- JavaScript は hasOwnProperty というプロパティ名を保護していません - MDN
JavaScriptで「staticイニシャライザー」ができるように
staticイニシャライザーとは、クラス定義時に初期化処理を一度だけ実行できるブロックのことです。Javaなどの世界ではすでにある仕組みです。
▼ 簡単な例
class MyClass {
static x;
static {
this.x = "こんにちは"
}
}
console.log(MyClass.x); // こんにちは
staticイニシャライザーがなぜ必要か?
ひょんなことから、enum
(列挙型)を作る必要が出てきたとしましょう。列挙型とは、似た値を集めたものです。たとえば、果物用の列挙型を作成し、FruitsEnum.apple
やFruitsEnum.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]
デモは次のとおりです。
関連資料
- class static initialization blocks - tc39
- ES2022 feature: class static initialization blocks - 2ality
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();
それぞれの関数内で、try
・catch
を使って例外をキャッチしましょう。このとき、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);
}
function1
とfunction2
でエラーが発生すると、それぞれ次のような出力になります。いずれも、エラーメッセージと、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の機能は、Safariを除く各環境で使用可能です。
- Google Chrome
- Firefox
- Microsoft Edge
- Node.js
「ECMAScript compatibility table」の「2022 features」で、細かい対応バージョンを確認できます。
Safariについて、Safari 16時点ではStaticイニシャライザーのみ未対応です。Safari Technology Preview 157より対応しました。
Release Notes for Safari Technology Preview 157 | WebKit
ES2022を使って便利に開発しよう
本記事では正式仕様としてリリースされたES2022の新機能を紹介しました。どれも開発をラクにしてくれるものばかりで、筆者も積極的に開発の現場で使っています。
ECMAScriptは次のES2023に向けて仕様策定がすでに始まっています。findLast()
やfindLastIndex()
など、また便利な機能が入ってきそうです。新しい機能をキャッチアップし、楽しく開発していきましょう。
ES2022のLanguage Specificationは、こちらから確認できます。
TwitterでもJavaScriptやCSSの最新情報を発信しています。ES2023以降の情報や、ステージ3以下の便利そうなJavaScriptなども紹介します。
Discussion
ES2022とは関係のない点を指摘することをお許しください。
この例は、
navigator.language.includes("en")
の部分があまりよくないと思います。仕様によると、
navigator.language
はBCP 47の言語タグを返すとされています。そしてBCP 47の言語タグは、次のような形式らしいです。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つのコードが例示されています。
これらは、try-catchブロックの
// どうする?
の部分に相当するコードだと認識しているのですが、その場合はPromise Chainのcatch
メソッドではなくて} catch (error) { ... }
ではないでしょうか?ありがとうございます!
ご指摘のとおりでしたので修正しました🙏