●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をマスターして、より優れたエンジニアを目指していきましょう。