JavaScriptの基礎 - 正規表現
概要
正規表現 (Regular Expression) は、文字列のパターンを記述するための特殊な構文であり、テキストの検索、マッチング、置換、検証といった操作を効率的に行うために使用する。
JavaScriptでは、正規表現は組み込みの RegExp オブジェクトとして実装されており、言語に深く統合されている。
正規表現を生成する方法は2種類ある。
| 生成方法 | 説明 | 用途 |
|---|---|---|
正規表現リテラル (/pattern/flags) |
スラッシュでパターンを囲む記法であり、 スクリプトの読み込み時にコンパイルされる |
パターンが固定の場合に適している。 |
new RegExp(pattern, flags) コンストラクタ |
RegExp コンストラクタにパターンと修飾子を文字列で渡す記法 |
パターンを動的に生成する場合やユーザ入力をパターンに含める場合に使用する。 |
JavaScriptの正規表現は、フラグにより動作を制御できる。
ES2015で u (Unicode) フラグと y (Sticky) フラグが追加され、
ES2018では s (Dot All) フラグ、名前付きキャプチャグループ、後読みアサーションが追加された。
ES2022では d (hasIndices) フラグ、ES2024では v (unicodeSets) フラグが追加された。
更にES2025では、任意の文字列を正規表現で安全に使用するための RegExp.escape() 静的メソッドと、正規表現の特定サブ式にのみフラグを適用するパターン修飾子が正式採用された。
正規表現の生成
正規表現リテラル
正規表現リテラルは、スラッシュ (/) でパターンを囲んで記述する。
スクリプトの読み込み時にコンパイルされるため、パターンが変化しない場合に使用することを推奨する。
// 基本的な正規表現リテラル
const re1 = /hello/;
const re2 = /hello/gi; // フラグ付き (g: 全マッチ, i: 大文字小文字を区別しない)
// テストの実行
console.log(re1.test("hello world")); // true
console.log(re2.test("HELLO WORLD")); // true
// 特殊文字はバックスラッシュでエスケープ
const reEscape = /https:\/\/example\.com/;
console.log(reEscape.test("https://example.com")); // true
RegExpコンストラクタ
RegExp コンストラクタは、パターンを文字列として渡して正規表現オブジェクトを生成する。
パターンを動的に構築する場合や変数の値をパターンに使用する場合に適している。
コンストラクタを使用する時の注意点として、バックスラッシュを文字列内でエスケープする必要があるため、\d は "\\d" と記述しなければならない。
// 基本的な使用方法
const re1 = new RegExp("hello");
const re2 = new RegExp("hello", "gi"); // フラグは第2引数に指定
// 動的なパターン生成
const keyword = "world";
const rePattern = new RegExp(keyword, "i");
console.log(rePattern.test("Hello World")); // true
// バックスラッシュのエスケープ
const reDigit = new RegExp("\\d+"); // \d は \\d と記述する
console.log(reDigit.test("abc123")); // true
// 既存の正規表現からの生成 (ES2015以降)
const original = /hello/gi;
const copy = new RegExp(original); // フラグも引き継ぐ
const modified = new RegExp(original, "i"); // フラグを上書き
フラグ
フラグ一覧
正規表現のフラグは、パターンマッチングの動作を制御する。
複数のフラグを組み合わせて使用できる。
| フラグ | 名称 | 説明 | 導入バージョン |
|---|---|---|---|
g |
Global | 最初のマッチだけでなく、全てのマッチを対象とする。 | ES1 |
i |
Case-Insensitive | 大文字と小文字を区別しない。 | ES1 |
m |
Multiline | ^ と $ を文字列全体の先頭・末尾ではなく、各行の先頭・末尾にマッチさせる。 |
ES1 |
s |
Dot All | ドット (.) が改行文字を含む全ての文字にマッチする。デフォルトでは改行文字にマッチしない。 |
ES2018 |
u |
Unicode | 完全なUnicodeマッチングを有効にする。 サロゲートペアを1文字として扱い、 \p{...} (Unicodeプロパティエスケープ) が使用可能になる。 |
ES2015 |
v |
Unicode Sets | u フラグの上位互換拡張文字クラスと集合演算 ( && 交差、-- 差) が使用可能になる。u フラグと同時使用不可。 |
ES2024 |
d |
Has Indices | マッチ結果に indices プロパティを追加する。各マッチおよびキャプチャグループの開始・終了インデックスを含む。 |
ES2022 |
y |
Sticky | 前のマッチが終了した位置 (lastIndex) からのみマッチを開始する。 |
ES2015 |
フラグの組み合わせ例
フラグは任意の組み合わせで使用できる。
// g + i : 大文字小文字を区別せず全マッチを取得
const str = "Hello hello HELLO";
const matches = str.match(/hello/gi);
console.log(matches); // ["Hello", "hello", "HELLO"]
// mフラグ : 複数行マッチング
const multiLine = "first\nsecond\nthird";
const reM = /^\w+/gm; // 各行の先頭にマッチ
console.log(multiLine.match(reM)); // ["first", "second", "third"]
// sフラグ : ドットが改行にもマッチ
const text = "start\nend";
console.log(/start.end/.test(text)); // false (sフラグなし)
console.log(/start.end/s.test(text)); // true (sフラグあり)
// uフラグ : Unicodeプロパティエスケープ
const reJapanese = /\p{Script=Hiragana}/u;
console.log(reJapanese.test("あ")); // true
// vフラグ : 集合演算 (ES2024)
const reLower = /[a-z--[a-c]]/v; // a-z から a, b, c を除外
console.log(reLower.test("d")); // true
console.log(reLower.test("a")); // false
// dフラグ : マッチインデックスの取得 (ES2022)
const reDateD = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/d;
const match = reDateD.exec("2025-02-20");
console.log(match.indices.groups);
// { year: [0, 4], month: [5, 7], day: [8, 10] }
主要メソッド
RegExp.prototype.test
test(string) は、文字列が正規表現にマッチするかを確認して、true または false を返す。
マッチの詳細情報は必要なく、存在確認のみを行う場合に使用する。
const re = /hello/i;
console.log(re.test("Hello World")); // true
console.log(re.test("Goodbye")); // false
// バリデーションの例
const emailRe = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
console.log(emailRe.test("user@example.com")); // true
console.log(emailRe.test("invalid-email")); // false
// gフラグとtest()の組み合わせ (lastIndexが更新される)
const reG = /hello/g;
console.log(reG.test("hello hello")); // true (lastIndex: 5)
console.log(reG.test("hello hello")); // true (lastIndex: 11)
console.log(reG.test("hello hello")); // false (lastIndex: 0にリセット)
String.prototype.match
match(regexp) は、文字列が正規表現にマッチした結果を返す。
g フラグ無しの場合は exec() と同等の詳細情報を含む配列を返し、
g フラグありの場合はマッチした文字列の配列のみを返す。
マッチしない場合は、null を返す。
const str = "Hello World 123";
// gフラグなし : 最初のマッチの詳細情報
const match1 = str.match(/\d+/);
console.log(match1[0]); // "123" (マッチした文字列)
console.log(match1.index); // 12 (マッチした位置)
console.log(match1.input); // "Hello World 123" (元の文字列)
// gフラグあり : 全マッチの配列 (詳細情報なし)
const str2 = "cat bat sat";
const match2 = str2.match(/[a-z]at/g);
console.log(match2); // ["cat", "bat", "sat"]
// マッチしない場合はnull
const match3 = str.match(/xyz/);
console.log(match3); // null
String.prototype.matchAll
matchAll(regexp) は、全マッチの詳細情報を含むイテレータを返す。
g フラグが必須であり、各マッチにインデックス情報とキャプチャグループが含まれる点が match() との大きな違いである。
ES2020で導入された。
const str = "test1 test2 test3";
// matchAllはイテレータを返す (gフラグ必須)
const re = /test(\d)/g;
const iter = str.matchAll(re);
// スプレッド構文で配列に変換
const allMatches = [...iter];
console.log(allMatches.length); // 3
console.log(allMatches[0][0]); // "test1" (全体マッチ)
console.log(allMatches[0][1]); // "1" (キャプチャグループ1)
console.log(allMatches[0].index); // 0 (マッチ位置)
// for...ofループで逐次処理
for (const match of str.matchAll(/test(\d)/g)) {
console.log(`${match[0]} at index ${match.index}`);
}
// test1 at index 0
// test2 at index 6
// test3 at index 12
String.prototype.replace / replaceAll
replace(pattern, replacement) は最初にマッチした部分を置換し、replaceAll(pattern, replacement) は全てのマッチを置換する。
replaceAll() に正規表現を渡す場合は g フラグが必須である。
置換文字列には特殊なパターン ($&, $1, $<name> 等) を使用でき、置換関数を渡すこともできる。
const str = "hello world";
// 文字列パターン : 最初のマッチのみ置換
console.log(str.replace("l", "L")); // "heLlo world"
// 正規表現 (gフラグ) : 全マッチを置換
console.log(str.replace(/l/g, "L")); // "heLLo worLd"
// replaceAll : 全マッチを置換 (gフラグ必須)
console.log(str.replaceAll("l", "L")); // "heLLo worLd"
// 特殊置換パターン
const str2 = "John Smith";
// $1, $2: キャプチャグループの参照
console.log(str2.replace(/(\w+)\s(\w+)/, "$2, $1")); // "Smith, John"
// 置換関数
const str3 = "hello world";
const result = str3.replace(/\b\w/g, (match) => match.toUpperCase());
console.log(result); // "Hello World"
// 名前付きキャプチャグループの参照 ($<name>)
const date = "2025-02-20";
const formatted = date.replace(
/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,
"$<year>年$<month>月$<day>日"
);
console.log(formatted); // "2025年02月20日"
String.prototype.search
search(regexp) は、最初にマッチした部分の開始インデックスを返す。
マッチしない場合は -1 を返す。
g フラグは無視される。
const str = "Hello World 123";
// マッチした位置のインデックスを返す
console.log(str.search(/\d+/)); // 12
console.log(str.search(/xyz/)); // -1 (マッチしない場合)
console.log(str.search(/world/i)); // 6 (iフラグで大文字小文字を区別しない)
// indexOf()との違い : 正規表現が使用可能
console.log(str.indexOf("World")); // 6
// console.log(str.indexOf(/World/)); // 正規表現は使用不可
String.prototype.split
split(separator, limit) は、正規表現をデリミタとして文字列を分割し、配列を返す。
キャプチャグループを含む正規表現を使用すると、デリミタ自体も結果配列に含まれる。
// 正規表現による分割
const str = "one1two2three3four";
console.log(str.split(/\d/)); // ["one", "two", "three", "four"]
// 複数の区切り文字
const csv = "a, b , c,d";
console.log(csv.split(/\s*,\s*/)); // ["a", "b", "c", "d"]
// キャプチャグループを含む分割 (デリミタが結果に含まれる)
const str2 = "Hello 1 World 2 JavaScript";
console.log(str2.split(/(\d)/));
// ["Hello ", "1", " World ", "2", " JavaScript"]
// limitパラメータ
console.log(str.split(/\d/, 2)); // ["one", "two"]
RegExp.prototype.exec
exec(string) は、文字列に対してマッチを実行し、最初にマッチした結果の詳細情報を配列として返す。
マッチしない場合は、null を返す。
g または y フラグ付きの場合、連続して呼び出すと lastIndex が更新され、次のマッチ位置から検索を継続できる。
const re = /(\d{4})-(\d{2})-(\d{2})/;
const str = "Date: 2025-02-20";
const result = re.exec(str);
console.log(result[0]); // "2025-02-20" (全体マッチ)
console.log(result[1]); // "2025" (グループ1)
console.log(result[2]); // "02" (グループ2)
console.log(result[3]); // "20" (グループ3)
console.log(result.index); // 6 (マッチ開始位置)
// gフラグ付きで繰り返し呼び出し
const reG = /test(\d)/g;
const str2 = "test1 test2 test3";
let match;
while ((match = reG.exec(str2)) !== null) {
console.log(`${match[0]} at index ${match.index}`);
}
// test1 at index 0
// test2 at index 6
// test3 at index 12
基本的なパターン
文字クラス
文字クラスは、特定の文字または文字の集合にマッチするパターンを表す。
| パターン | 説明 | マッチする例 |
|---|---|---|
. |
任意の1文字にマッチする。 改行文字を除く。 s フラグを付けると改行も含む。 |
/a.c/ は "abc", "a1c", "a_c" にマッチ |
\d |
数字 [0-9] にマッチする。 | /\d+/ は "123" にマッチ
|
\D |
数字以外にマッチする。 [^0-9] と等価 |
/\D+/ は "abc" にマッチ |
\w |
英数字とアンダースコア [a-zA-Z0-9_] にマッチする。 | /\w+/ は "hello_123" にマッチ |
\W |
\w 以外にマッチする。 |
/\W/ は "@", "!" にマッチ |
\s |
空白文字 (スペース、タブ、改行等) にマッチする。 | /\s+/ は " " にマッチ |
\S |
空白文字以外にマッチする。 | /\S+/ は "hello" にマッチ |
[abc] |
a, b, cのいずれか1文字にマッチする。 | /[aeiou]/ は "a", "e" にマッチ |
[^abc] |
a, b, c以外の1文字にマッチする。 | /[^aeiou]/ は "b", "c" にマッチ |
[a-z] |
a から zの範囲の1文字にマッチする。 | /[a-z]/ は "a", "m", "z" にマッチ |
\p{...} |
UNICODEプロパティエスケープu または v フラグが必須 |
/\p{Script=Hiragana}/u は "あ" にマッチ |
量指定子
量指定子は、直前のパターンが何回繰り返されるかを指定する。
デフォルトは 貪欲マッチ (できるだけ多くの文字にマッチ) であり、? を追加すると 非貪欲マッチ (できるだけ少ない文字にマッチ) になる。
| パターン | 説明 | 例 |
|---|---|---|
* |
0回以上繰り返す。(貪欲) | /a*/ は "", "a", "aaa" にマッチ |
+ |
1回以上繰り返す。(貪欲) | /a+/ は "a", "aaa" にマッチ |
? |
0回または1回 (貪欲) | /a?/ は "", "a" にマッチ |
{n} |
n回繰り返す。 | /a{3}/ は "aaa" にのみマッチ |
{n,} |
n回以上繰り返す。(貪欲) | /a{2,}/ は "aa", "aaa" にマッチ |
{n,m} |
n回以上m回以下繰り返す。(貪欲) | /a{2,4}/ は "aa", "aaa", "aaaa" にマッチ |
*? |
0回以上繰り返す。(非貪欲) | /a*?/ は最小限の文字にマッチ |
+? |
1回以上繰り返す。(非貪欲) | /a+?/ は最小限の文字にマッチ |
?? |
0回または1回 (非貪欲) | /a??/ は、まず0回を試みる。 |
// 貪欲マッチ vs 非貪欲マッチ
const str = "<a>hello</a>";
console.log(str.match(/<.+>/)[0]); // "<a>hello</a>" (貪欲: 最大限マッチ)
console.log(str.match(/<.+?>/)[0]); // "<a>" (非貪欲: 最小限マッチ)
// 量指定子の例
const re1 = /\d{4}/; // ちょうど4桁の数字
const re2 = /\d{2,4}/; // 2〜4桁の数字
console.log(re1.test("2025")); // true
console.log(re1.test("123")); // false
console.log(re2.test("12")); // true
console.log(re2.test("12345")); // true (最初の4桁にマッチ)
アンカー
アンカーは、文字列内の特定の位置にマッチするパターンであり、文字自体にはマッチしない。
^- 文字列の先頭にマッチする。
mフラグ使用時は各行の先頭にマッチする。
$- 文字列の末尾にマッチする。
mフラグ使用時は各行の末尾にマッチする。
\b- 単語の境界にマッチする。
- 単語文字 (
\w) と非単語文字の間の位置を指す。
\B- 単語の境界以外の位置にマッチする。
// ^ と $ による完全一致バリデーション
const re = /^\d{3}-\d{4}$/;
console.log(re.test("123-4567")); // true
console.log(re.test("X123-4567")); // false (先頭が数字でない)
console.log(re.test("123-4567 extra")); // false (末尾に余分な文字)
// mフラグで複数行対応
const multiLine = "first line\nsecond line";
console.log(multiLine.match(/^\w+/gm)); // ["first", "second"]
// \bによる単語境界マッチ
const str = "cat concatenate cats";
console.log(str.match(/\bcat\b/g)); // ["cat"] (単独の "cat" のみ)
console.log(str.match(/cat/g)); // ["cat", "cat", "cat"] (全ての "cat")
選択とグループ化
選択 (|) は複数のパターンのいずれかにマッチし、グループ化 ((?:...)) は非キャプチャグループを作成する。
// 選択 (|): 複数のパターンのいずれかにマッチ
const re1 = /cat|dog|bird/;
console.log(re1.test("I have a cat")); // true
console.log(re1.test("I have a fish")); // false
// グループ化で量指定子を適用
const re2 = /(ha)+/; // "ha" の繰り返し
console.log(re2.test("hahaha")); // true
// 非キャプチャグループ (?:...)
const re3 = /(?:https?|ftp):\/\//;
console.log(re3.test("https://example.com")); // true
console.log(re3.test("ftp://example.com")); // true
// 選択とグループ化の組み合わせ
const re4 = /^(?:Mr|Mrs|Ms)\.?\s+[A-Z][a-z]+/;
console.log(re4.test("Mr. Smith")); // true
console.log(re4.test("Mrs Johnson")); // true
キャプチャグループ
番号付きキャプチャグループ
括弧 (...) で囲んだ部分はキャプチャグループとなり、マッチした文字列を後で参照できる。
グループは左から順に 1, 2, 3 ... と番号が付けられ、match() や exec() の結果配列のインデックスで参照できる。
// 基本的なキャプチャグループ
const re = /(\d{4})-(\d{2})-(\d{2})/;
const match = re.exec("2025-02-20");
console.log(match[0]); // "2025-02-20" (全体マッチ)
console.log(match[1]); // "2025" (グループ1: 年)
console.log(match[2]); // "02" (グループ2: 月)
console.log(match[3]); // "20" (グループ3: 日)
// キャプチャグループを使用した置換
const date = "2025-02-20";
const formatted = date.replace(/(\d{4})-(\d{2})-(\d{2})/, "$3/$2/$1");
console.log(formatted); // "20/02/2025"
// ネストしたキャプチャグループ
const reNested = /(a(b))(c)/;
const matchNested = reNested.exec("abc");
console.log(matchNested[0]); // "abc"
console.log(matchNested[1]); // "ab" (外側のグループ)
console.log(matchNested[2]); // "b" (内側のグループ)
console.log(matchNested[3]); // "c"
名前付きキャプチャグループ
名前付きキャプチャグループ ((?<name>pattern)) は、ES2018で導入された機能である。
グループに名前を付けることにより、番号ではなく名前で結果にアクセスでき、コードの可読性が向上する。
マッチ結果の groups プロパティから名前でアクセスできる。
// 名前付きキャプチャグループ (ES2018)
const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = re.exec("2025-02-20");
// groupsプロパティから名前でアクセス
console.log(match.groups.year); // "2025"
console.log(match.groups.month); // "02"
console.log(match.groups.day); // "20"
// 分割代入と組み合わせる
const { year, month, day } = match.groups;
console.log(`${year}年${month}月${day}日`); // "2025年02月20日"
// 名前付きグループを使用した置換 ($<name>)
const date = "2025-02-20";
const formatted = date.replace(
/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,
"$<year>年$<month>月$<day>日"
);
console.log(formatted); // "2025年02月20日"
// matchAllでの使用
const str = "2025-01-01 and 2025-12-31";
for (const match of str.matchAll(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/g)) {
console.log(match.groups);
}
// { year: "2025", month: "01", day: "01" }
// { year: "2025", month: "12", day: "31" }
後方参照
後方参照は、同じ正規表現内で以前にマッチしたキャプチャグループの内容を再度参照する機能である。
番号付きグループは \1, \2 で参照し、名前付きグループは \k<name> で参照する。
// 番号付き後方参照
// 同じ文字の繰り返しを検出
const reDouble = /(\w)\1/;
console.log(reDouble.test("aa")); // true ("a" が2回)
console.log(reDouble.test("ab")); // false (異なる文字)
console.log(reDouble.test("lool")); // true ("o" が2回)
// 開始タグと終了タグが一致するHTMLタグのマッチ
const reTag = /<(\w+)>.*<\/\1>/;
console.log(reTag.test("<p>Hello</p>")); // true
console.log(reTag.test("<p>Hello</div>")); // false (タグが不一致)
// 名前付き後方参照 (\k<name>)
const reNamed = /(?<word>\w+) \k<word>/;
console.log(reNamed.test("hello hello")); // true (同じ単語の繰り返し)
console.log(reNamed.test("hello world")); // false
先読みと後読み
肯定先読みと否定先読み
先読み (Lookahead) は、特定のパターンの後に何が続くかを条件としてマッチを行う。
先読み自体は文字を消費しない (ゼロ幅アサーション) ため、マッチした文字列には先読みの内容が含まれない。
| 種類 | 構文 | 説明 |
|---|---|---|
| 肯定先読み | (?=...) |
指定したパターンが後に続く場合にマッチする。 |
| 否定先読み | (?!...) |
指定したパターンが後に続かない場合にマッチする。 |
// 肯定先読み (?=...) : 後に特定のパターンが続く場合にマッチ
const str = "100px 200em 300px";
const re1 = /\d+(?=px)/g; // px が後に続く数字にマッチ
console.log(str.match(re1)); // ["100", "300"]
// 否定先読み (?!...) : 後に特定のパターンが続かない場合にマッチ
const re2 = /\d+(?!px)/g; // px が後に続かない数字にマッチ
console.log(str.match(re2)); // ["200", "10", "30"] (部分マッチを含む)
// パスワード強度チェック (複数の先読みを組み合わせる)
const passwordRe = /^(?=.*[A-Z])(?=.*[a-z])(?=.*\d).{8,}$/;
console.log(passwordRe.test("Password1")); // true
console.log(passwordRe.test("password1")); // false (大文字なし)
console.log(passwordRe.test("PASSWORD1")); // false (小文字なし)
console.log(passwordRe.test("Pass")); // false (8文字未満)
// 数値のカンマ区切り
const num = "1234567890";
const formatted = num.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
console.log(formatted); // "1,234,567,890"
肯定後読みと否定後読み
後読み (Lookbehind) は、特定のパターンの前に何があるかを条件としてマッチを行う。
ES2018で導入された機能であり、Chrome v62+, Firefox v78+, Safari v16.6+ でサポートされている。
後読みも先読みと同様にゼロ幅アサーションであり、マッチした文字列に後読みの内容は含まれない。
| 種類 | 構文 | 説明 |
|---|---|---|
| 肯定後読み | (?<=...) |
指定したパターンが前にある場合にマッチする。 |
| 否定後読み | (?<!...) |
指定したパターンが前にない場合にマッチする。 |
// 肯定後読み (?<=...) : 前に特定のパターンがある場合にマッチ
const str = "$100 £200 $300";
const re1 = /(?<=\$)\d+/g; // $ の後に続く数字にマッチ
console.log(str.match(re1)); // ["100", "300"]
// 否定後読み (?<!...) : 前に特定のパターンがない場合にマッチ
const re2 = /(?<!\$)\d+/g; // $ の後でない数字にマッチ
console.log(str.match(re2)); // ["200"]
// 実用例 : プレフィックスを除いた値の抽出
const prices = "Price: $50, Discount: -$10, Total: $40";
const amounts = prices.match(/(?<=\$)\d+/g);
console.log(amounts); // ["50", "10", "40"]
// 先読みと後読みの組み合わせ
const str2 = "start_content_end";
const re3 = /(?<=start_).+(?=_end)/;
console.log(str2.match(re3)[0]); // "content"
ES2025の新機能
RegExp.escape
RegExp.escape(string) は、文字列内の正規表現の特殊文字をエスケープする静的メソッドである。
ES2025で正式採用された。
ユーザ入力や動的なデータを正規表現のパターンとして使用する時、
特殊文字 (*, +, ?, ., (, ) 等) をエスケープしないと意図しないマッチングが発生する場合がある。
RegExp.escape() を使用することにより、この問題を安全に解決できる。
Webブラウザの対応状況は、Safari、Firefoxが対応済みであり、Chromeへの実装提案が2025年2月時点で着陸している。
// RegExp.escape()の基本的な使用方法
const userInput = "a*b.c+d";
const escaped = RegExp.escape(userInput);
console.log(escaped); // "a\*b\.c\+d" (特殊文字がエスケープされる)
// 動的なパターン生成での活用
const keyword = "price (USD)";
const re = new RegExp(RegExp.escape(keyword), "i");
console.log(re.test("Total price (USD): $100")); // true
// RegExp.escape()を使わない危険な例
const dangerInput = ".*";
const dangerRe = new RegExp(dangerInput);
console.log(dangerRe.test("anything")); // true (意図しない全マッチ)
// RegExp.escape()を使用した安全な例
const safeInput = ".*";
const safeRe = new RegExp(RegExp.escape(safeInput));
console.log(safeRe.test("anything")); // false
console.log(safeRe.test(".*")); // true (リテラルとしてマッチ)
正規表現パターン修飾子
正規表現パターン修飾子は、正規表現全体ではなく特定のサブ式にのみフラグを適用する機能である。
シンタックスは、(?flags:subexpression) であり、- を使用してフラグを無効化することもできる。
Stage 4として2024年10月8日に承認され、ES2025に採用された。
サポートされるフラグは、i (大文字・小文字区別なし)、m (複数行)、s (ドットオール) の3種類である。
// 基本的な使用方法 : サブ式にのみ i フラグを適用
const re1 = /^x(?i:HELLO)x$/;
console.log(re1.test("xhellox")); // true (HELLOが大文字小文字区別なし)
console.log(re1.test("XhelloX")); // false (xは大文字小文字を区別する)
// - でフラグを無効化 (正規表現全体にiフラグを付けつつ、部分的に無効化)
const re2 = /^(?-i:X)hello$/i; // 全体はiフラグ、Xの部分のみ区別あり
console.log(re2.test("Xhello")); // true
console.log(re2.test("xhello")); // false (小文字xはマッチしない)
// 複数フラグの組み合わせ
const re3 = /start(?im:^data)end/;
// mフラグとiフラグを部分的に適用
// 実用例 : 大文字小文字を部分的に区別するパターン
const logRe = /\[ERROR\](?i: .+)/;
console.log(logRe.test("[ERROR] something went wrong")); // true
console.log(logRe.test("[ERROR] FATAL ERROR OCCURRED")); // true
console.log(logRe.test("[error] something went wrong")); // false
パターン例
バリデーション
正規表現はフォーム入力のバリデーションに広く使用される。
ただし、URLのバリデーションには URL オブジェクトの使用を推奨する。
| 用途 | パターン | 備考 |
|---|---|---|
| メールアドレス | /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ |
基本的なメール形式の検証 |
| 電話番号 (国際形式) | /^\+?[1-9]\d{1,14}$/ |
E.164形式に準拠 |
| 郵便番号 (日本) | /^\d{3}-?\d{4}$/ |
ハイフンあり・なし両対応 |
| 半角英数字のみ | /^[a-zA-Z0-9]+$/ |
パスワード文字種の確認 |
| URLの簡易チェック | /^https?:\/\/.+/ |
URL オブジェクト使用を推奨 |
| 日付 (YYYY-MM-DD) | 1[0-2])-(0[1-9]|[12]\d|3[01])$/ | 月・日の範囲チェック付き |
| 16進数カラーコード | [0-9a-fA-F]{6})$/ | 3桁・6桁に対応 |
| 日本語文字の検出 | /[\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Han}]/u |
u フラグ必須 |
// メールアドレスのバリデーション
const emailRe = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
console.log(emailRe.test("user@example.com")); // true
console.log(emailRe.test("invalid@")); // false
// 日本の郵便番号
const zipRe = /^\d{3}-?\d{4}$/;
console.log(zipRe.test("123-4567")); // true
console.log(zipRe.test("1234567")); // true
// 日本語文字の検出
const jpRe = /[\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Han}]/u;
console.log(jpRe.test("Hello World")); // false
console.log(jpRe.test("こんにちは")); // true
console.log(jpRe.test("漢字テスト")); // true
// パスワード強度チェック (大文字・小文字・数字を各1文字以上含む8文字以上)
const passRe = /^(?=.*[A-Z])(?=.*[a-z])(?=.*\d).{8,}$/;
console.log(passRe.test("Password1")); // true
console.log(passRe.test("weakpass")); // false
文字列の加工
正規表現はテキストの変換や抽出にも活用できる。
// 数値のカンマ区切りフォーマット
function formatNumber(num) {
return String(num).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
console.log(formatNumber(1234567)); // "1,234,567"
// HTMLのエスケープ
function escapeHtml(str) {
return str
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
console.log(escapeHtml('<script>alert("XSS")</script>'));
// <script>alert("XSS")</script>
// キャメルケースをスネークケースに変換
function toSnakeCase(str) {
return str
.replace(/([A-Z])/g, "_$1")
.toLowerCase()
.replace(/^_/, "");
}
console.log(toSnakeCase("helloWorldFoo")); // "hello_world_foo"
// スネークケースをキャメルケースに変換
function toCamelCase(str) {
return str.replace(/_([a-z])/g, (match, char) => char.toUpperCase());
}
console.log(toCamelCase("hello_world_foo")); // "helloWorldFoo"
// 連続する空白を1つにまとめる
const str = "Hello World JavaScript";
console.log(str.replace(/\s+/g, " ")); // "Hello World JavaScript"
// URLからドメイン名を抽出
const url = "https://www.example.co.jp/path?query=value";
const domain = url.match(/^https?:\/\/([^/]+)/)[1];
console.log(domain); // "www.example.co.jp"
関連情報
- JavaScriptの基礎 - 文字列
- テンプレートリテラル、文字列メソッド、タグ付きテンプレートリテラル
- JavaScriptの基礎 - エラーハンドリング
- try / catch、カスタムエラー、エラーの伝播