String.prototype.matchAll()

matchAll()正規表現に一致するすべての文字列を、キャプチャグループを含み、イテレーターで返すメソッドです。

試してみましょう

構文

js
matchAll(regexp)

引数

regexp

正規表現オブジェクト、または Symbol.matchAll を持つ任意のオブジェクトです。

regexpRegExp 以外のオブジェクトであった場合、暗黙的に RegExp への変換が new RegExp(regexp, 'g') を使用して行われます。

regexp正規表現である場合、グローバルフラグ (g) が設定されます。そうでなければ TypeError が発生します。

返値

一致したものの反復可能なイテレーターオブジェクト(再起動不可能なもの)です。それぞれの一致する配列は RegExp.prototype.exec() の返値と同じ形です。

例外

TypeError

regexp正規表現である場合で、グローバルフラグ (g) が設定されていない場合(flags プロパティに "g" が含まれていない場合)。

解説

String.prototype.matchAll の実装自体は、正規表現がグローバルであるという余分な入力検証を除けば)非常にシンプルで、引数の文字列を最初の引数として Symbol.matchAll メソッドを呼び出すだけです。実際の実装は RegExp.prototype[@@matchAll]() から来ています。

Regexp.prototype.exec() と matchAll()

matchAll() が JavaScript に追加される前は、 regexp.exec(および /g フラグ付きの正規表現)をループの中で呼び出すことですべての一致結果を取得することができました。

js
const regexp = /foo[a-z]*/g;
const str = "table football, foosball";
let match;

while ((match = regexp.exec(str)) !== null) {
  console.log(
    `Found ${match[0]} start=${match.index} end=${regexp.lastIndex}.`,
  );
}
// Found football start=6 end=14.
// Found foosball start=16 end=24.

matchAll() が使えるようになったことで、 while によるループと、g 付きの exec を避けることができます。代わりにイテレーターが取得できるので、for...of配列スプレッドArray.from() 構造と効率よく組み合わせることができます。

js
const regexp = /foo[a-z]*/g;
const str = "table football, foosball";
const matches = str.matchAll(regexp);

for (const match of matches) {
  console.log(
    `Found ${match[0]} start=${match.index} end=${
      match.index + match[0].length
    }.`,
  );
}
// Found football start=6 end=14.
// Found foosball start=16 end=24.

// 一致したイテレーターは for...of の反復処理の後で利用不可になる
// 新しいイテレーターを作成するために matchAll を再度呼び出す
Array.from(str.matchAll(regexp), (m) => m[0]);
// [ "football", "foosball" ]

matchAll は、グローバル (g) フラグがない場合は例外が発生します。

js
const regexp = /[a-c]/;
const str = "abc";
str.matchAll(regexp);
// TypeError

matchAll では内部的に regexp の複製を作成します。そのため regexp.exec() とは違って文字列をスキャンした際に lastIndex が変わることはありません。

js
const regexp = /[a-c]/g;
regexp.lastIndex = 1;
const str = "abc";
Array.from(str.matchAll(regexp), (m) => `${regexp.lastIndex} ${m[0]}`);
// [ "1 b", "1 c" ]

しかし、これは regexp.exec() をループ内で使うのとは異なり、正規表現を進めたり戻したりするために lastIndex を変更することができないことを意味します。

キャプチャリンググループへのより良いアクセス(String.prototype.match() との比較)

matchAll はキャプチャグループへのよりよいアクセスを実現します。

match() では、グローバル g フラグを使用するとキャプチャグループが無視されてしまいます。

js
const regexp = /t(e)(st(\d?))/g;
const str = "test1test2";

str.match(regexp); // ['test1', 'test2']

matchAll を使えば簡単にキャプチャグループにアクセスできます。

js
const array = [...str.matchAll(regexp)];

array[0];
// ['test1', 'e', 'st1', '1', index: 0, input: 'test1test2', length: 4]
array[1];
// ['test2', 'e', 'st2', '2', index: 5, input: 'test1test2', length: 4]

matchAll() を RegExp ではない @@matchAll を実装しているオブジェクトで使用

オブジェクトに Symbol.matchAll メソッドがあれば、それをカスタムマッチャーとして使うことができます。Symbol.matchAll の返値は matchAll() の返値となる。

js
const str = "Hmm, this is interesting.";

str.matchAll({
  [Symbol.matchAll](str) {
    return [["Yes, it's interesting."]];
  },
}); // [["Yes, it's interesting."]] を返す

仕様書

Specification
ECMAScript Language Specification
# sec-string.prototype.matchall

ブラウザーの互換性

BCD tables only load in the browser

関連情報