はじめに (対象読者・この記事でわかること)
このページは、JavaScript の正規表現に慣れつつあったが、マッチした一部だけを柔軟に置換したいという方を対象としています。
- 正規表現の基本構文は知っているが、String.prototype.replace の第2引数に関数を渡す意味が分からない人
- 既存コードで部分置換ができず、手作業で文字列を分割・結合している人
この記事を読むと、マッチした文字列をキャプチャグループで取得し、コールバック関数を使って動的に置換文字列を生成できるようになります。また、実務で役立つパターン別コード例と、よく遭遇する落とし穴とその対処法も紹介します。
前提知識
この記事を読み進める上で、以下の知識があるとスムーズです。
- 基本的な HTML と JavaScript の文法
- 正規表現のメタ文字(.、*、+、?、[]、() など)の意味
- String.prototype.replace の基本的な使い方(文字列、正規表現の両方で置換できる)
正規表現で部分置換が必要になるシーンと基本概念
Web アプリケーションでは、ユーザー入力や外部データを加工する際に 「特定パターンだけを書き換える」 必要が頻繁にあります。たとえば、メールアドレスのドメインを一括置換したり、URL のクエリ文字列だけをマスクしたりするケースです。
JavaScript の replace メソッドは二通りの書き方があります。
- 文字列(または正規表現) + 置換文字列
str.replace(/cat/g, 'dog')→ 文字列全体が静的に置換されます。 - 文字列(または正規表現) + 関数
str.replace(/(\d{4})-(\d{2})-(\d{2})/, (m, y, mth, d) =>${d}/${mth}/${y})
2 番目は、マッチした部分ごとにコールバックが呼び出され、キャプチャグループが引数として渡されます。この仕組みを利用すれば、「マッチした文字列だけを加工」でき、置換文字列を動的に決定できます。
主要ポイント
| ポイント | 説明 |
|---|---|
| キャプチャグループ | () で囲んだ部分が replace のコールバックに渡され、$1, $2 でも参照可能 |
| 全体マッチと部分マッチ | 正規表現に g フラグがあると全体に対して繰り返しマッチし、各回でコールバックが実行 |
| 置換関数のシグネチャ | (match, p1, p2, ..., offset, string) ただし p1, p2 … はキャプチャグループ、offset はマッチ位置、string は元文字列 |
正規表現マッチ部分だけを置換する実装ステップ
以下では、実務でよく使う 3 つのシナリオを例に、ステップごとにコードと解説を行います。
シナリオ 1: メールアドレスのドメインを一括置換
ステップ 1: 正規表現とキャプチャの作成
Js// メールアドレス全体からローカル部とドメイン部を分離 const emailPattern = /([a-zA-Z0-9._%+-]+)@([a-zA-Z0-9.-]+\.[a-z]{2,})/g;
- 第 1 キャプチャ
([a-zA-Z0-9._%+-]+)がローカル部 - 第 2 キャプチャ
([a-zA-Z0-9.-]+\.[a-z]{2,})がドメイン部
ステップ 2: 置換関数でドメインだけ変更
Jsfunction replaceDomain(text, newDomain) { return text.replace(emailPattern, (match, local, domain) => { // 置換対象がローカル部でなくドメインだけなので、ローカル部はそのまま利用 return `${local}@${newDomain}`; }); } // 使用例 const src = "alice@example.com, bob@test.org"; console.log(replaceDomain(src, "newdomain.jp")); // => "alice@newdomain.jp, bob@newdomain.jp"
ポイント:
- match は完全マッチ文字列(例: alice@example.com)
- local と domain がキャプチャグループとして渡されるので、domain を無視し新しいドメイン文字列を組み立てるだけで完了。
シナリオ 2: 日付文字列のフォーマット変換(YYYY-MM-DD → DD/MM/YYYY)
ステップ 1: 正規表現で年月日を捕捉
Jsconst datePattern = /(\d{4})-(\d{2})-(\d{2})/g;
- 第 1 キャプチャは年
(\d{4}) - 第 2、3 は月・日
ステップ 2: コールバックで順序入れ替え
Jsfunction formatDate(str) { return str.replace(datePattern, (match, y, m, d) => `${d}/${m}/${y}`); } // 使用例 console.log(formatDate("Today is 2025-08-14.")); // => "Today is 14/08/2025."
注意点
- replace の第2引数は文字列テンプレートでも書けるが、関数を使うと計算ロジックや条件分岐を付加できる。たとえば「年が 2020 年以前は別フォーマットにする」なども容易。
シナリオ 3: URL のクエリパラメータ名だけをマスク(?token=abc123 → ?token=****)
ステップ 1: クエリ文字列全体をキャプチャし、個々のパラメータに分割
Jsconst queryPattern = /([?&])([^=&#]+)=([^&#]*)/g;
- 第 1 キャプチャは前置文字
?または& - 第 2 はパラメータ名
- 第 3 はパラメータ値
ステップ 2: パラメータ名が token のときだけマスク
Jsfunction maskToken(url) { return url.replace(queryPattern, (match, prefix, name, value) => { if (name.toLowerCase() === 'token') { return `${prefix}${name}=****`; } return match; // それ以外は元の文字列をそのまま返す }); } // 使用例 console.log(maskToken("https://example.com/api?user=alice&token=abc123&mode=edit")); // => "https://example.com/api?user=alice&token=****&mode=edit"
ハマった点やエラー解決
| 発生した問題 | 原因 | 解決策 |
|---|---|---|
置換後に ? が二重になる |
replace が prefix(? か &)をそのまま残したため |
prefix を必ず保持しつつ、条件分岐でマスク/非マスクを判定 |
| 正規表現が非貪欲にマッチしない | .* が全体を捕捉し、次のパラメータまで飲み込んだ |
パラメータ値は [^&#]*(&・# 以外)で制限 |
g フラグが無いと最初のマッチしか置換しない |
replace はデフォルトで最初の 1 回だけ実行 |
正規表現リテラルに g フラグを付与 |
パフォーマンスTips
- 大量の文字列を置換する場合、正規表現オブジェクトを再利用するとエンジンがコンパイルコストを削減できます。例:
const re = /.../g;をモジュールスコープで定義。 - 置換ロジックが複雑になるときは、
String.prototype.replaceAll(ES2021)よりもreplace+gフラグの方が広くサポートされている点に注意。
まとめ
本記事では、JavaScript の正規表現と replace メソッドを組み合わせて、マッチした部分だけを柔軟に置換する手法を解説しました。
- 正規表現のキャプチャグループと置換関数を利用すれば、動的に生成した文字列で部分置換が可能。
- メールドメイン変更、日付フォーマット変換、URL クエリマスクといった実務シナリオで即活用できるコード例を示した。
- 落とし穴(
gフラグ忘れ、非貪欲マッチの失敗、プレフィックス二重化)とその対策を網羅した。
この記事を通じて、正規表現置換の真の威力と安全な実装パターンを身につけていただけたら幸いです。次回は「正規表現と Unicode 正規化、絵文字を安全に置換するテクニック」を取り上げる予定です。
参考資料
- MDN Web Docs – String.prototype.replace
- MDN Web Docs – 正規表現 (RegExp)
- 「JavaScript正規表現デザインパターン」 (O'Reilly, 2022)
- Effective JavaScript – 書き方のベストプラクティス