【入門編】 matchAll() による正規表現の全一致取得 – JavaScript実践ガイド

皆さん、こんにちは!チーフアーキテクトの〇〇です。
今日もJavaScriptの奥深い世界へ、一緒に旅を始めましょう。

Web制作や開発の現場で、文字列の中から「特定のパターン」を見つけ出す作業って、本当に頻繁に遭遇しますよね。例えば、ブログ記事の中から日付を全部抜き出したり、商品名と価格のペアを一覧で取得したり……。

そんな時、「よし、正規表現と`String.prototype.match()`で!」と意気込んでコードを書いてみたものの、「あれ?なんで最初の一個しか見つからないんだろう…」とか、「全部見つかったのはいいけど、肝心な中身(キャプチャグループ)が取り出しにくいぞ…」なんて、頭を抱えた経験、ありませんか?

大丈夫です、その気持ち、痛いほどよく分かります。私も若かりし頃、同じ壁にぶつかっては、夜な夜なコーヒー片手に唸っていたものです。
でも、安心してください。そんな「あと一歩!」の悩みを、まるで魔法のように解決してくれる、素晴らしいメソッドがあるんです。それが、今回ご紹介する`String.prototype.matchAll()`!

今日のテーマは、この`matchAll()`メソッドを使って、文字列の中から正規表現に「全一致」する結果を、まるで宝箱から一つずつお宝を取り出すように、丁寧に、そして便利に取得する方法です。初学者の方にも分かりやすいように、身近な例え話を交えながら、優しくひも解いていきましょう。

—

探偵さん、全部見つけて!`matchAll()`って一体何者?

想像してみてください。あなたは巨大なショッピングモールの中にいます。そして、あなたには「特定のお店(例えば、”カフェ”や”レストラン”という名前のお店)を全部見つけて、そのお店の名前とフロア番号を控えてくる」というミッションが与えられました。

普通の`match()`メソッド(特に正規表現に`g`フラグがない場合)は、まるで「最初に”カフェ”を見つけたら、そこで満足して報告に戻ってしまう探偵さん」のようなものです。一つ見つけたらおしまい!

でも、今回ご紹介する`matchAll()`は違います。
`matchAll()`は、まさに「どんなに奥まった場所にあっても、指定されたお店を根気強く探し出し、見つけたお店の【名前】と【フロア番号】、さらには【地図上の位置】まで、一つずつ丁寧にメモしてきてくれる、超優秀な探偵さん」なんです。

しかも、そのメモは、必要な時に必要なだけ見せてくれる「お買い物リスト」のような形式で渡してくれます。これがJavaScriptで言うところの「イテレータ」という概念なのですが、難しく考えなくて大丈夫。「一つずつ順番に、必要なものだけ取り出せるリスト」とイメージしてください。

`matchAll()`が特に嬉しいポイント

1. 全部見つけてくれる!: 正規表現に`g`(グローバル)フラグさえ付けておけば、文字列の中から一致するパターンを漏れなく全て見つけ出してくれます。
2. 詳細情報が豊富!: 一致した文字列そのものだけでなく、正規表現の「キャプチャグループ」(特定の情報だけを切り抜く枠)の中身、文字列中の何番目で見つかったか(インデックス)、どの文字列から見つかったか、といった詳細な情報がセットで手に入ります。
3. 効率的!: 全ての情報を一度にメモリに載せるのではなく、必要な時に一つずつ取り出せる「イテレータ」形式なので、特に大きな文字列を扱う際にメモリを節約できるんです。

—

さっそく実践!`matchAll()`を使ってみよう

では、具体的なコードを見ていきましょう。
今回は、とあるお店のリストから「商品名とその価格」を全部抜き出すことを目標にします。

const shoppingList = “りんご:150円, みかん:100円, バナナ:200円, ぶどう:300円”;

// 正規表現で「商品名」と「価格」を探すパターンを定義します。
// (\w+): 商品名(任意の文字が1つ以上)をキャプチャグループ1として取得
// : コロン(区切り文字)
// (\d+)円: 価格(数字が1つ以上)をキャプチャグループ2として取得
// g: グローバルフラグ。文字列全体から全ての一致を探します。
// i: 大文字小文字を区別しないフラグ(今回は不要ですが、よく使われます)
const regex = /(\w+):(\d+)円/g;

// matchAll() を使って、文字列の中から全ての一致を探します
// これが「お買い物リスト」の元となるイテレータです
const matchesIterator = shoppingList.matchAll(regex);

console.log(“— 1. for…of で一つずつ取り出す方法 —“);
// for…of ループを使って、イテレータから一つずつマッチ結果を取り出します
// まるで、スーパーのレジで一つずつ商品をスキャンしていくようなイメージです
for (const match of matchesIterator) {
// match は配列のようなオブジェクトで、様々な情報を含んでいます
// match[0] は、正規表現全体に一致した文字列 (“りんご:150円” など)
// match[1] は、キャプチャグループ1に一致した文字列 (商品名 “りんご” など)
// match[2] は、キャプチャグループ2に一致した文字列 (価格 “150” など)
// match.index は、一致した文字列が元の文字列のどこから始まったか
// match.input は、検索対象となった元の文字列全体

console.log(`全体一致: ${match[0]}`); // 例: りんご:150円
console.log(`商品名: ${match[1]}`); // 例: りんご
console.log(`価格: ${match[2]}円`); // 例: 150円
console.log(`見つかった位置(インデックス): ${match.index}`);
console.log(‘—‘);
}

// 注意: イテレータは一度消費されると中身が空になります。
// なので、もう一度 matchAll() を呼び出すか、
// 配列に変換したものを使い回す必要があります。

// もう一度 matchAll() を呼び出して、新しいイテレータを取得
const anotherMatchesIterator = shoppingList.matchAll(regex);

console.log(“\n— 2. Array.from() で一気に配列に変換する方法 —“);
// イテレータを Array.from() で一気に配列に変換することもできます
// これは、お買い物リストを全部写真に撮って、後でゆっくり見返すようなイメージです
const allMatchesArray = Array.from(anotherMatchesIterator);

// 配列なので、forEach や map など、おなじみの配列メソッドが使えます
allMatchesArray.forEach((match, index) => {
console.log(`[${index + 1}個目の商品情報]`);
console.log(`商品名: ${match[1]}`);
console.log(`価格: ${match[2]}円`);
console.log(‘—‘);
});

このコードを実行すると、コンソールには以下のような情報が出力されます。

— 1. for…of で一つずつ取り出す方法 —
全体一致: りんご:150円
商品名: りんご
価格: 150円
見つかった位置(インデックス): 0
—
全体一致: みかん:100円
商品名: みかん
価格: 100円
見つかった位置(インデックス): 10
—
全体一致: バナナ:200円
商品名: バナナ
価格: 200円
見つかった位置(インデックス): 20
—
全体一致: ぶどう:300円
商品名: ぶどう
価格: 300円
見つかった位置(インデックス): 30
—

— 2. Array.from() で一気に配列に変換する方法 —
[1個目の商品情報]
商品名: りんご
価格: 150円
—
[2個目の商品情報]
商品名: みかん
価格: 100円
—
[3個目の商品情報]
商品名: バナナ
価格: 200円
—
[4個目の商品情報]
商品名: ぶどう
価格: 300円
—

どうでしょう?欲しい情報が、とてもきれいに、しかもまとめて手に入りましたよね!これなら、複雑なデータからの情報抽出も、ぐっと楽になります。

名前付きキャプチャグループで、もっと分かりやすく!

先ほどの例では`match[1]`や`match[2]`のように、数字でキャプチャグループの中身を取り出しました。でも、正規表現が複雑になってくると、「あれ、`[1]`って何だったっけ…?」となりがちです。

そんな時に便利なのが、「名前付きキャプチャグループ」です。まるで、探偵さんが見つけた情報に「これは商品名だよ」「これは価格だよ」と付箋を貼ってくれるようなものですね。

const shoppingList = “りんご:150円, みかん:100円, バナナ:200円, ぶどう:300円”;

// 名前付きキャプチャグループを使って正規表現を定義します。
// ? で「productName」という名前を付けます
// ? で「price」という名前を付けます
const regexWithNames = /(?\w+):(?\d+)円/g;

const matchesIterator = shoppingList.matchAll(regexWithNames);

console.log(“\n— 3. 名前付きキャプチャグループで取り出す方法 —“);
for (const match of matchesIterator) {
// match.groups オブジェクトから、名前でアクセスできます
console.log(`商品名: ${match.groups.productName}`);
console.log(`価格: ${match.groups.price}円`);
console.log(‘—‘);
}

これで、より直感的で読みやすいコードになりましたね!現場では、この名前付きキャプチャグループを積極的に使うことで、コードの可読性が飛躍的に向上し、後から見返した時の「あれ?」が格段に減ります。

—

現場でつまずきやすいポイント:`g`フラグは必須!

ここで、一点だけ、皆さんに強くお伝えしたい注意点があります。
それは、`matchAll()`を使う際には、正規表現に必ず`g`(グローバル)フラグを付けること!

もし`g`フラグを付け忘れると、JavaScriptは怒ってエラーを出してしまいます。

const text = “apple banana apple”;
const regexNoG = /apple/; // gフラグがない!

try {
text.matchAll(regexNoG); // エラーが発生します!
} catch (e) {
console.error(“\n— エラー発生!gフラグを付け忘れるとこうなります —“);
console.error(e.message); // -> “RegExp.prototype.@@matchAll called with a non-global RegExp argument”
console.error(“そうです、正規表現にgフラグを付けないと怒られちゃいます!”);
}

「え、どうして?」って思いますよね。
これは、`matchAll()`が文字列の最初から最後まで、何度も検索を繰り返すことを想定しているからです。`g`フラグがない正規表現は、最初のマッチを見つけたら「もう検索終わり!」と判断してしまうため、`matchAll()`の「全部見つける」という役割と合致しないんですね。だから、「この正規表現じゃ、私の仕事ができないよ!」とJavaScriptが教えてくれるわけです。

だから、`matchAll()`を使う時は、正規表現の最後に忘れずに`/g`を付けましょう! これが、現場で無駄な時間を消費しないための、ちょっとした秘訣ですよ。

—

`matchAll()`の活用シーンは無限大!

この`matchAll()`は、皆さんが想像する以上に、様々な場面でその真価を発揮します。

  • ブログ記事のメタデータ抽出: 大量の記事コンテンツから、投稿日、著者名、タグなどを一括で抜き出す。
  • マークダウンやBBCodeの解析: 記事内の`[リンク](URL)`や`強調`といった記法をまとめて抽出し、HTMLに変換する。
  • ログファイルの分析: エラーメッセージのパターンや、特定のユーザーIDの行動履歴を効率的に集計する。
  • APIレスポンスからのデータ抽出: 複雑なJSON文字列やXMLから、必要な情報をピンポイントで複数抜き出す。
  • フォーム入力の複数チェック: 例えば、ユーザーが入力したタグの文字列から、不正な形式のタグを全て見つけ出す。

これらはほんの一部です。JavaScriptで文字列を扱う以上、必ずどこかで「複数のパターンを見つけたい」という場面に遭遇します。そんな時、この`matchAll()`があなたの強力な武器となることでしょう。

—

まとめ:`matchAll()`はあなたの情報抽出の頼れる相棒!

今日の話をまとめると、

  • `matchAll()`は、文字列の中から正規表現に全一致する結果を、詳細な情報付きで、一つずつ丁寧に取り出してくれるメソッドです。
  • 結果は「イテレータ」という、必要な時に必要なだけ取り出せる便利な形式で提供されます。
  • `for…of`ループや`Array.from()`を使って、その結果を扱うことができます。
  • 名前付きキャプチャグループを使うと、コードの可読性が格段に向上します。
  • そして何より、正規表現には必ず`g`(グローバル)フラグを付けること! これを忘れるとエラーになります。

最初は正規表現自体に戸惑うこともあるかもしれません。記号の羅列に見えて、ちょっと難しそう…と感じるのも、ごく自然なことです。でも、大丈夫。一つずつ、自分が「こんな情報が欲しいな」と思った時に、少しずつ試してみる。その積み重ねが、やがてあなたのスキルを大きく成長させてくれます。

`matchAll()`は、まさに「痒い所に手が届く」メソッドであり、Web制作・開発の現場で、皆さんの生産性を飛躍的に向上させてくれるでしょう。この強力なツールを使いこなして、文字列操作の達人を目指してください!

これからも、一緒にJavaScriptの奥深い世界を楽しく探求していきましょう!
また次の記事でお会いしましょう!

コメント

タイトルとURLをコピーしました