JavaScriptにおけるmatchAllメソッドの使い方と注意点10選

JavaScriptのmatchAllメソッドを使って文字列を検索しようJS
この記事は約19分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

●matchAllメソッドとは

JavaScriptの文字列操作において、正規表現を使った検索は非常に強力なツールとなります。

ES2020で導入されたmatchAllメソッドは、その正規表現による検索をさらに便利にしてくれる存在です。

○matchAllの基本的な使い方

matchAllメソッドは、文字列に対して正規表現によるマッチングを行い、マッチした部分文字列を含む特殊な配列を返します。

この配列は、正規表現にマッチしたすべての部分文字列を含んでいるのが特徴です。

○サンプルコード1:matchAllの基本形

const str = "Hello world! Hello matchAll!";
const regex = /Hello/g;
const matches = str.matchAll(regex);

for (const match of matches) {
  console.log(match);
}

実行結果

["Hello", index: 0, input: "Hello world! Hello matchAll!", groups: undefined]
["Hello", index: 13, input: "Hello world! Hello matchAll!", groups: undefined]

このように、matchAllメソッドを使うことで、正規表現にマッチしたすべての部分文字列を簡単に取得できます。

○matchAllとmatchの違い

ところで、皆さんは従来のmatchメソッドをご存知でしょうか?

実はmatchAllメソッドは、このmatchメソッドの発展形とも言えます。

○サンプルコード2:matchとの比較

const str = "Hello world! Hello matchAll!";
const regex1 = /Hello/;
const regex2 = /Hello/g;

console.log(str.match(regex1));
console.log(str.match(regex2));
console.log([...str.matchAll(regex2)]);

実行結果

["Hello", index: 0, input: "Hello world! Hello matchAll!", groups: undefined]
["Hello", "Hello"]
[
  ["Hello", index: 0, input: "Hello world! Hello matchAll!", groups: undefined],
  ["Hello", index: 13, input: "Hello world! Hello matchAll!", groups: undefined]
]

matchメソッドは、グローバルフラグ(g)なしの正規表現を渡すと、最初にマッチした部分文字列の情報のみを返します。

一方、グローバルフラグありの正規表現では、マッチしたすべての部分文字列を含む配列を返しますが、それぞれのマッチ情報は含まれません。

それに対してmatchAllメソッドは、グローバルフラグありの正規表現を渡すことで、マッチしたすべての部分文字列とそれぞれの詳細情報を取得できるのです。

●matchAllの注意点

matchAllメソッドは非常に便利な機能ですが、使う上でいくつか注意点があります。

この注意点を理解しておくことで、より効果的にmatchAllを活用できるでしょう。

○サンプルコード3:グローバルフラグの必要性

matchAllメソッドを使う際に最も注意すべき点は、必ずグローバルフラグ(g)付きの正規表現を使う必要があるということです。

const str = "Hello world! Hello matchAll!";
const regex1 = /Hello/;
const regex2 = /Hello/g;

console.log(str.matchAll(regex1)); // Error
console.log(str.matchAll(regex2)); // OK

実行結果

TypeError: String.prototype.matchAll called with a non-global RegExp argument
Object [RegExp String Iterator] {}

グローバルフラグなしの正規表現をmatchAllに渡すと、TypeErrorが発生します。

これは、matchAllがグローバルマッチを前提としているためです。

○サンプルコード4:再利用できないIterator

matchAllメソッドの返り値はIteratorオブジェクトです。

このIteratorは一度使い切ると、再利用できないという特性があります。

const str = "Hello world! Hello matchAll!";
const regex = /Hello/g;
const matches = str.matchAll(regex);

for (const match of matches) {
  console.log(match);
}

for (const match of matches) {
  console.log(match); // 出力されない
}

実行結果

["Hello", index: 0, input: "Hello world! Hello matchAll!", groups: undefined]
["Hello", index: 13, input: "Hello world! Hello matchAll!", groups: undefined]

2回目のループでは何も出力されません。

これは、Iteratorが一度使い切られたためです。

再度matchAllを呼び出せば、新しいIteratorを取得できます。

○サンプルコード5:Iteratorから配列への変換

IteratorをそのままAPIに渡せない場合など、Iteratorを配列に変換したいケースがあります。

その場合は、スプレッド構文を使って簡単に変換できます。

const str = "Hello world! Hello matchAll!";
const regex = /Hello/g;
const matches = str.matchAll(regex);
const arrayMatches = [...matches];

console.log(arrayMatches);

実行結果

[
  ["Hello", index: 0, input: "Hello world! Hello matchAll!", groups: undefined],
  ["Hello", index: 13, input: "Hello world! Hello matchAll!", groups: undefined]
]

Iteratorを配列に変換することで、添字アクセスや長さの取得など、配列のメソッドを使えるようになります。

○サンプルコード6:未定義の場合の挙動

正規表現にマッチしない文字列をmatchAllに渡すと、どうなるでしょうか。

const str = "Hello world!";
const regex = /Goodbye/g;
const matches = str.matchAll(regex);

console.log(matches.next().value);

実行結果

undefined

マッチしない場合、matchAllはnext()メソッドを呼び出すとundefinedを返すIteratorを返します。

このように、マッチしない場合の挙動も把握しておくと良いでしょう。

●matchAllの活用例

正規表現を使った文字列検索は、データの抽出や変換など、様々な場面で活躍します。

ここでは、matchAllメソッドを使った実践的な活用例をいくつか見ていきましょう。

○サンプルコード7:キャプチャグループの利用

正規表現のキャプチャグループを使うと、マッチした部分文字列の一部を取り出すことができます。

matchAllメソッドと組み合わせることで、この機能をさらに強力に活用できます。

const str = "John Doe, Jane Doe, Jim Doe";
const regex = /(\w+)\s(\w+)/g;

for (const match of str.matchAll(regex)) {
  console.log(`姓: ${match[2]}, 名: ${match[1]}`);
}

実行結果

姓: Doe, 名: John
姓: Doe, 名: Jane
姓: Doe, 名: Jim

このように、キャプチャグループを使うことで、名前と姓を別々に抽出することができます。

matchAllメソッドのおかげで、複数のマッチを簡単に処理できるのです。

○サンプルコード8:名前付きキャプチャグループ

キャプチャグループには、名前を付けることもできます。

これにより、マッチ結果の可読性が向上します。

const str = "John Doe, Jane Doe, Jim Doe";
const regex = /(?<firstName>\w+)\s(?<lastName>\w+)/g;

for (const match of str.matchAll(regex)) {
  console.log(`姓: ${match.groups.lastName}, 名: ${match.groups.firstName}`);
}

実行結果

姓: Doe, 名: John
姓: Doe, 名: Jane
姓: Doe, 名: Jim

名前付きキャプチャグループを使うと、マッチ結果のgroupsプロパティから、グループ名で値を取得できます。

これにより、コードの意図が明確になり、可読性が上がります。

○サンプルコード9:後方参照の利用

正規表現の後方参照機能を使うと、キャプチャグループにマッチした文字列を再利用できます。

matchAllメソッドと組み合わせれば、より柔軟な文字列処理が可能です。

const str = "hello world, HELLO WORLD, Hello World";
const regex = /(\w+)\s(\w+)(?=.*\1\s\2)/gi;

for (const match of str.matchAll(regex)) {
  console.log(`マッチ: ${match[0]}`);
}

実行結果

マッチ: hello world
マッチ: HELLO WORLD

この正規表現は、同じ単語の組み合わせが後ろに続く場合にのみマッチします。

後方参照を使うことで、このような複雑なパターンマッチングを実現できるのです。

○サンプルコード10:Iteratorのその他のメソッド

matchAllメソッドが返すIteratorオブジェクトには、next()メソッド以外にもいくつかの便利なメソッドがあります。

const str = "One 1, Two 2, Three 3";
const regex = /(\w+)\s(\d)/g;
const matches = str.matchAll(regex);

console.log(matches.next().value);
console.log(Array.from(matches));

実行結果

["One 1", "One", "1", index: 0, input: "One 1, Two 2, Three 3", groups: undefined]
[
  ["Two 2", "Two", "2", index: 7, input: "One 1, Two 2, Three 3", groups: undefined],
  ["Three 3", "Three", "3", index: 14, input: "One 1, Two 2, Three 3", groups: undefined]
]

Iteratorのnext()メソッドを直接呼び出すことで、一つずつマッチ結果を取得できます。

また、Array.from()メソッドを使えば、Iteratorを配列に変換することもできます。

●よくあるエラーと対処法

matchAllメソッドを使っていると、時々予期せぬエラーに遭遇することがあります。

そうした場面で冷静に対処できるよう、よくあるエラーとその対処法を把握しておきましょう。

○TypeError: String.prototype.matchAll called with a non-global RegExp argument

const str = "Hello World!";
const regex = /Hello/;
const matches = str.matchAll(regex); // TypeError

このエラーは、グローバルフラグ(g)のない正規表現をmatchAllに渡した時に発生します。

matchAllメソッドは、マッチしたすべての部分文字列を返すため、グローバルサーチを前提としています。

したがって、グローバルフラグのない正規表現を渡すとエラーになるのです。

対処法は簡単で、正規表現リテラルにgフラグを付けるか、RegExpコンストラクタの第2引数に”g”を指定するだけです。

const str = "Hello World!";
const regex1 = /Hello/g;
const regex2 = new RegExp("Hello", "g");
const matches1 = str.matchAll(regex1); // OK
const matches2 = str.matchAll(regex2); // OK

このように修正すれば、エラーが解消されます。

○TypeError: matchAll is not a function

const str = "Hello World!";
str.matchAll(/Hello/g); // TypeError

このエラーは、matchAllメソッドをサポートしていない古いブラウザ環境で発生します。

matchAllはES2020で導入された比較的新しいメソッドなので、古いブラウザではサポートされていない可能性があります。

この問題に対処するには、matchAllのポリフィル(互換性のための代替実装)を使う方法があります。

if (!String.prototype.matchAll) {
  String.prototype.matchAll = function(regex) {
    // matchAllの簡易的なポリフィル実装
    "use strict";
    if (this == null) throw new TypeError("String.prototype.matchAll called on null or undefined");
    if (!(regex instanceof RegExp)) throw new TypeError("regexp must be a RegExp");
    let flags = "g";
    if (regex.global) flags = flags.replace("g", "");
    regex = new RegExp(regex, flags);
    const matches = [];
    while (true) {
      const match = regex.exec(this);
      if (match == null) break;
      matches.push(match);
    }
    return matches[Symbol.iterator]();
  }
}

このようなポリフィルを使えば、古いブラウザでもmatchAllメソッドを使えるようになります。

ただし、ポリフィルはあくまで暫定的な解決策です。

可能であれば、matchAllをネイティブサポートしているモダンなブラウザを使うことをおすすめします。

○returned value is not an array

const str = "Hello World!";
const matches = str.matchAll(/Hello/g);
console.log(matches[0]); // undefined

matchAllメソッドの戻り値はIteratorオブジェクトであり、配列ではありません。

したがって、matchAllの戻り値に対して配列のようにインデックスアクセスしようとすると、undefinedが返ってきます。

Iteratorを配列に変換するには、Array.from()メソッドやスプレッド構文を使います。

const str = "Hello World!";
const matches = str.matchAll(/Hello/g);
const array = [...matches];
console.log(array[0]); // ["Hello", index: 0, input: "Hello World!", groups: undefined]

このようにして、マッチ結果を配列として扱うことができます。

適切に対処する方法を知っていれば、怖くはありません。

●matchAllの応用的な使い方

正規表現とmatchAllメソッドの組み合わせは、文字列処理の強力な武器になります。

ここでは、matchAllをさらに応用的に使う方法をいくつか見ていきましょう。

○サンプルコード11:重複文字列の抽出

文章の中から、重複している部分文字列を見つけ出すことを考えます。

これは、校正作業などで役立つテクニックです。

const str = "This is a pen. That is a pencil. This is an apple.";
const regex = /(\b\w+\b)(?=.*\b\1\b)/g;
const matches = str.matchAll(regex);

for (const match of matches) {
  console.log(`重複した単語: ${match[0]}`);
}

実行結果

重複した単語: is
重複した単語: a
重複した単語: This

この正規表現は、単語の境界(\b)で囲まれた1個以上の単語構成文字(\w+)にマッチします。

さらに、先読みアサーション(?=.*\b\1\b)で、マッチした単語(\1)が後方にも存在することを確認しています。

このように、matchAllと正規表現を駆使することで、重複文字列の抽出を簡潔に実装できます。

○サンプルコード12:HTML内の特定タグの抽出

HTMLソースから特定のタグで囲まれた部分を抽出してみましょう。

これは、ウェブスクレイピングなどでよく使われる処理です。

const html = `
  <html>
    <body>
      <h1>Hello, World!</h1>
      <p>This is a <strong>sample</strong> HTML.</p>
      <p>matchAll is useful for <strong>string processing</strong>.</p>
    </body>
  </html>
`;

const regex = /<p>(.*?)<\/p>/gs;
const matches = html.matchAll(regex);

for (const match of matches) {
  console.log(match[1]);
}

実行結果

This is a <strong>sample</strong> HTML.
matchAll is useful for <strong>string processing</strong>.

この正規表現は、<p>と</p>に囲まれた部分にマッチします。

.*?は、非貪欲なマッチングを行うので、最短のマッチが得られます。

matchAllを使えば、HTMLから必要な情報だけを簡単に抽出できます。

○サンプルコード13:文字列置換への応用

matchAllの結果を使って、文字列の置換を行うこともできます。

ここでは、その一例を見てみましょう。

const str = "apple, banana, cherry, apple, durian, apple";
const regex = /apple/g;
const replaced = str.replace(regex, (match) => {
  return `<strong>${match}</strong>`;
});

console.log(replaced);

実行結果

<strong>apple</strong>, banana, cherry, <strong>apple</strong>, durian, <strong>apple</strong>

この例では、replaceメソッドのコールバック関数内で、マッチした部分文字列を<strong>タグで囲んでいます。

同様の処理をmatchAllを使って書くこともできます。

const str = "apple, banana, cherry, apple, durian, apple";
const regex = /apple/g;
const matches = str.matchAll(regex);
let result = str;

for (const match of matches) {
  result = result.slice(0, match.index) + `<strong>${match[0]}</strong>` + result.slice(match.index + match[0].length);
}

console.log(result);

matchAllの結果を使って、マッチした位置や文字列を特定しながら置換を行っています。

○サンプルコード14:RegExpオブジェクトとの併用

RegExpオブジェクトのメソッドと組み合わせることで、matchAllをさらに柔軟に使えます。

const str = "The quick brown fox jumps over the lazy dog.";
const regex = /quick (brown).+?(lazy) dog/g;

if (regex.test(str)) {
  const matches = str.matchAll(regex);
  for (const match of matches) {
    console.log(`Matched: ${match[0]}`);
    console.log(`Group 1: ${match[1]}`);
    console.log(`Group 2: ${match[2]}`);
  }
} else {
  console.log("No matches found.");
}

実行結果

Matched: quick brown fox jumps over the lazy dog
Group 1: brown
Group 2: lazy

この例では、まずRegExpオブジェクトのtestメソッドで、正規表現にマッチする部分文字列が存在するかどうかを確認しています。

マッチする場合のみ、matchAllを使って詳細な情報を取得しています。

まとめ

matchAllメソッドは、正規表現を使った高度な文字列操作を可能にするJavaScriptの強力な機能です。

本記事では、matchAllの基本から応用まで、実例を交えて詳しく解説してきました。

matchAllを使いこなすことで、データ処理の効率と品質を大きく向上できるでしょう。

文字列処理のスキルは、モダンなJavaScript開発に欠かせない能力です。

正規表現とmatchAllをマスターして、より優れたエンジニアを目指していきましょう。